From c370fe0ce05473c3bccd9e4e085bc93769be4f7a Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Tue, 21 Jan 2025 00:05:24 -0800 Subject: [PATCH 01/44] Refactor media artwork handling and async updates Updated `PlatformUpdateSource` to be asynchronous, re-implemented `SetPoster` method for robustness, and replaced `MetadataArtworkUrl` with `MetadataArtworkSource` across the project. - Replaced `MetadataArtworkUrl` with `MetadataArtworkSource` in `MediaElementPage.xaml` and related files. - Introduced `loadCustomMediaSource` constant and `saveDirectory` string in `MediaElementPage.xaml.cs`. - Updated `ChangeSourceClicked` method to handle new media source and artwork property. - Added file handling methods: `Savefile`, `GetFileName`, and `PickAndShow`. - Updated `IMediaElement` and `MediaElement` class to use `MetadataArtworkSource`. - Modified `SetMetadata` in `Metadata.macios.cs` for new artwork property. - Removed `Metadata` class from `Metadata.windows.cs`. - Enhanced `MediaManager.android.cs` to handle new artwork property and fetch image data. - Added `BlankByteArray` method and `PlaybackState` class in `MediaManager.android.cs`. - Made `PlatformUpdateSource` in `MediaManager.macios.cs` asynchronous, updated `Dispose` method. - Added `GetArtwork` struct for fetching artwork in `MediaManager.macios.cs`. - Updated `MediaManager.windows.cs` to handle new artwork property and added `ArtworkUrl` method. - Updated `MediaElementTests` for new artwork property. --- .../Views/MediaElement/MediaElementPage.xaml | 2 +- .../MediaElement/MediaElementPage.xaml.cs | 82 ++++++++- .../Resources/Raw/robot.jpg | Bin 0 -> 183423 bytes .../Interfaces/IMediaElement.cs | 4 +- .../MediaElement.shared.cs | 16 +- .../Primitives/Metadata.macios.cs | 97 +++++++++-- .../Primitives/Metadata.windows.cs | 72 -------- .../Views/MediaManager.android.cs | 164 +++++++++++++----- .../Views/MediaManager.macios.cs | 97 ++++++----- .../Views/MediaManager.windows.cs | 71 +++++--- .../Views/MediaElement/MediaElementTests.cs | 10 +- 11 files changed, 389 insertions(+), 226 deletions(-) create mode 100644 samples/CommunityToolkit.Maui.Sample/Resources/Raw/robot.jpg delete mode 100644 src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml index 609ce11a3e..6bab85d185 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml @@ -30,7 +30,7 @@ x:Name="MediaElement" ShouldAutoPlay="True" Source="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" - MetadataArtworkUrl="https://lh3.googleusercontent.com/pw/AP1GczNRrebWCJvfdIau1EbsyyYiwAfwHS0JXjbioXvHqEwYIIdCzuLodQCZmA57GADIo5iB3yMMx3t_vsefbfoHwSg0jfUjIXaI83xpiih6d-oT7qD_slR0VgNtfAwJhDBU09kS5V2T5ZML-WWZn8IrjD4J-g=w1792-h1024-s-no-gm" + MetadataArtworkSource="https://lh3.googleusercontent.com/pw/AP1GczNRrebWCJvfdIau1EbsyyYiwAfwHS0JXjbioXvHqEwYIIdCzuLodQCZmA57GADIo5iB3yMMx3t_vsefbfoHwSg0jfUjIXaI83xpiih6d-oT7qD_slR0VgNtfAwJhDBU09kS5V2T5ZML-WWZn8IrjD4J-g=w1792-h1024-s-no-gm" MetadataTitle="Big Buck Bunny" MetadataArtist="Blender Foundation" MediaEnded="OnMediaEnded" diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index 819e1c9441..a574359b79 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -16,7 +16,8 @@ public partial class MediaElementPage : BasePage const string loadLocalResource = "Load Local Resource"; const string resetSource = "Reset Source to null"; const string loadMusic = "Load Music"; - + const string loadCustomMediaSource = "Load Custom Image Source"; + static readonly string saveDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); const string buckBunnyMp4Url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; const string botImageUrl = "https://lh3.googleusercontent.com/pw/AP1GczNRrebWCJvfdIau1EbsyyYiwAfwHS0JXjbioXvHqEwYIIdCzuLodQCZmA57GADIo5iB3yMMx3t_vsefbfoHwSg0jfUjIXaI83xpiih6d-oT7qD_slR0VgNtfAwJhDBU09kS5V2T5ZML-WWZn8IrjD4J-g=w1792-h1024-s-no-gm"; const string hlsStreamTestUrl = "https://mtoczko.github.io/hls-test-streams/test-gap/playlist.m3u8"; @@ -161,13 +162,13 @@ void Button_Clicked(object? sender, EventArgs e) async void ChangeSourceClicked(Object sender, EventArgs e) { var result = await DisplayActionSheet("Choose a source", "Cancel", null, - loadOnlineMp4, loadHls, loadLocalResource, resetSource, loadMusic); + loadOnlineMp4, loadHls, loadLocalResource, resetSource, loadMusic, loadCustomMediaSource); switch (result) { case loadOnlineMp4: MediaElement.MetadataTitle = "Big Buck Bunny"; - MediaElement.MetadataArtworkUrl = botImageUrl; + MediaElement.MetadataArtworkSource = MediaSource.FromUri(botImageUrl); MediaElement.MetadataArtist = "Big Buck Bunny Album"; MediaElement.Source = MediaSource.FromUri(buckBunnyMp4Url); @@ -175,20 +176,20 @@ async void ChangeSourceClicked(Object sender, EventArgs e) case loadHls: MediaElement.MetadataArtist = "HLS Album"; - MediaElement.MetadataArtworkUrl = botImageUrl; + MediaElement.MetadataArtworkSource = botImageUrl; MediaElement.MetadataTitle = "HLS Title"; MediaElement.Source = MediaSource.FromUri(hlsStreamTestUrl); return; case resetSource: - MediaElement.MetadataArtworkUrl = string.Empty; + MediaElement.MetadataArtworkSource = string.Empty; MediaElement.MetadataTitle = string.Empty; MediaElement.MetadataArtist = string.Empty; MediaElement.Source = null; return; case loadLocalResource: - MediaElement.MetadataArtworkUrl = botImageUrl; + MediaElement.MetadataArtworkSource = MediaSource.FromResource("robot.jpg"); MediaElement.MetadataTitle = "Local Resource Title"; MediaElement.MetadataArtist = "Local Resource Album"; @@ -210,10 +211,57 @@ async void ChangeSourceClicked(Object sender, EventArgs e) case loadMusic: MediaElement.MetadataTitle = "HAL 9000"; MediaElement.MetadataArtist = "HAL 9000 Album"; - MediaElement.MetadataArtworkUrl = botImageUrl; + MediaElement.MetadataArtworkSource = botImageUrl; MediaElement.Source = MediaSource.FromUri(hal9000AudioUrl); return; + case loadCustomMediaSource: + var fileresult = await PickAndShow(new PickOptions + { + PickerTitle = "Pick a media file", + FileTypes = FilePickerFileType.Images, + }); + var fileName = await Savefile(fileresult); + if (fileName is not null) + { + MediaElement.MetadataArtworkSource = MediaSource.FromFile(fileName); + MediaElement.MetadataTitle = "Big Buck Bunny"; + MediaElement.MetadataArtist = "Big Buck Bunny Album"; + MediaElement.Source = + MediaSource.FromUri(buckBunnyMp4Url); + } + + return; + } + } + static async Task Savefile(FileResult? fileresult) + { + if (fileresult is null) + { + System.Diagnostics.Trace.WriteLine("File result is null"); + return null; + } + try + { + using Stream fileStream = await fileresult.OpenReadAsync(); + using StreamReader reader = new(fileStream); + var fileName = GetFileName(fileresult.FileName); + using FileStream output = File.Create(fileName); + await fileStream.CopyToAsync(output); + fileStream.Seek(0, SeekOrigin.Begin); + FileStream.Synchronized(output); + return fileName; + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + return null; } + + } + static string GetFileName(string name) + { + var filename = Path.GetFileName(name); + return Path.Combine(saveDirectory, filename); } async void ChangeAspectClicked(object? sender, EventArgs e) @@ -274,4 +322,24 @@ void DisplayPopup(object sender, EventArgs e) popupMediaElement.Handler?.DisconnectHandler(); }; } + static async Task PickAndShow(PickOptions options) + { + try + { + var result = await FilePicker.Default.PickAsync(options); + if (result is not null) + { + using var stream = await result.OpenReadAsync(); + var image = ImageSource.FromStream(() => stream); + } + + return result; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + } + + return null; + } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/Resources/Raw/robot.jpg b/samples/CommunityToolkit.Maui.Sample/Resources/Raw/robot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d531def8d2208a080c387ad848fe577e7d63af61 GIT binary patch literal 183423 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%0Dtz*;1+stsLjt4CS@-Kyl_@h1! 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 z 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 UkIJx*O4wz1yA*=Cj*a$w3 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 literal 0 HcmV?d00001 diff --git a/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs b/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs index 05e3ce7f51..c46c813f38 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs @@ -19,9 +19,9 @@ public interface IMediaElement : IView, IAsynchronousMediaElementHandler string MetadataArtist { get; set; } /// - /// Gets or sets the artwork Image Url. + /// Gets or sets the artwork Image source. /// - string MetadataArtworkUrl { get; set; } + public MediaSource? MetadataArtworkSource { get; set; } /// /// Gets the media aspect ratio. diff --git a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs index b23d291711..bcd495c2aa 100644 --- a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs +++ b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs @@ -116,10 +116,10 @@ public partial class MediaElement : View, IMediaElement, IDisposable public static readonly BindableProperty MetadataArtistProperty = BindableProperty.Create(nameof(MetadataArtist), typeof(string), typeof(MediaElement), string.Empty); /// - /// Backing store for the property. + /// Backing store for the property. /// - public static readonly BindableProperty MetadataArtworkUrlProperty = BindableProperty.Create(nameof(MetadataArtworkUrl), typeof(string), typeof(MediaElement), string.Empty); - + public static readonly BindableProperty MetadataArtworkSourceProperty = BindableProperty.Create(nameof(MetadataArtworkSource), typeof(MediaSource), typeof(MediaElement)); + readonly WeakEventManager eventManager = new(); readonly SemaphoreSlim seekToSemaphoreSlim = new(1, 1); @@ -362,13 +362,14 @@ public string MetadataArtist } /// - /// Gets or sets the Artwork Image Url of the media. + /// Gets or sets the Artwork Image Source of the media. /// This is a bindable property. /// - public string MetadataArtworkUrl + [TypeConverter(typeof(MediaSourceConverter))] + public MediaSource? MetadataArtworkSource { - get => (string)GetValue(MetadataArtworkUrlProperty); - set => SetValue(MetadataArtworkUrlProperty, value); + get => (MediaSource)GetValue(MetadataArtworkSourceProperty); + set => SetValue(MetadataArtworkSourceProperty, value); } /// @@ -577,7 +578,6 @@ void ClearTimer() timer.Stop(); timer = null; } - void OnSourceChanged(object? sender, EventArgs eventArgs) { OnPropertyChanged(SourceProperty.PropertyName); diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index d0dc3ec0d6..f58458578c 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -1,4 +1,5 @@ using AVFoundation; +using CommunityToolkit.Maui.Views; using CoreMedia; using Foundation; using MediaPlayer; @@ -69,40 +70,32 @@ public Metadata(PlatformMediaElement player) /// /// /// - public void SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElement) + public async Task SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElement) { if (mediaElement is null) { - Metadata.ClearNowPlaying(); return; } + ClearNowPlaying(); + var artwork = await MetadataArtworkUrl(mediaElement.MetadataArtworkSource).ConfigureAwait(false); + if (artwork is not null && artwork is UIImage image) + { + NowPlayingInfo.Artwork = new(boundsSize: new(320, 240), requestHandler: _ => image); + } + else + { + NowPlayingInfo.Artwork = new(boundsSize: new(0, 0), requestHandler: _ => defaultUIImage); + } NowPlayingInfo.Title = mediaElement.MetadataTitle; NowPlayingInfo.Artist = mediaElement.MetadataArtist; NowPlayingInfo.PlaybackDuration = playerItem?.Duration.Seconds ?? 0; NowPlayingInfo.IsLiveStream = false; NowPlayingInfo.PlaybackRate = mediaElement.Speed; NowPlayingInfo.ElapsedPlaybackTime = playerItem?.CurrentTime.Seconds ?? 0; - NowPlayingInfo.Artwork = new(boundsSize: new(320, 240), requestHandler: _ => GetImage(mediaElement.MetadataArtworkUrl)); MPNowPlayingInfoCenter.DefaultCenter.NowPlaying = NowPlayingInfo; } - static UIImage GetImage(string imageUri) - { - try - { - if (imageUri.StartsWith(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) - { - return UIImage.LoadFromData(NSData.FromUrl(new NSUrl(imageUri))) ?? defaultUIImage; - } - return defaultUIImage; - } - catch - { - return defaultUIImage; - } - } - MPRemoteCommandHandlerStatus SeekCommand(MPRemoteCommandEvent? commandEvent) { if (commandEvent is not MPChangePlaybackPositionCommandEvent eventArgs) @@ -179,4 +172,70 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) return MPRemoteCommandHandlerStatus.Success; } + + public static async Task MetadataArtworkUrl(MediaSource? artworkUrl, CancellationToken cancellationToken = default) + { + if (artworkUrl is UriMediaSource uriMediaSource) + { + var uri = uriMediaSource.Uri; + return GetBitmapFromUrl(uri?.AbsoluteUri); + } + else if (artworkUrl is FileMediaSource fileMediaSource) + { + var uri = fileMediaSource.Path; + + return await GetBitmapFromFile(uri, cancellationToken).ConfigureAwait(false); + } + else if (artworkUrl is ResourceMediaSource resourceMediaSource) + { + var path = resourceMediaSource.Path; + return await GetBitmapFromResource(path, cancellationToken).ConfigureAwait(false); + } + return null; + } + + static async Task GetBitmapFromFile(string? resource, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(resource)) + { + return null; + } + using var fileStream = File.OpenRead(resource); + using var memoryStream = new MemoryStream(); + await fileStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; + NSData temp = NSData.FromStream(memoryStream) ?? new NSData(); + return UIImage.LoadFromData(temp); + } + static UIImage? GetBitmapFromUrl(string? resource) + { + if (string.IsNullOrEmpty(resource)) + { + return null; + } + return UIImage.LoadFromData(NSData.FromUrl(new NSUrl(resource))); + } + static async Task GetBitmapFromResource(string? resource, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(resource)) + { + return null; + } + using var inputStream = await FileSystem.OpenAppPackageFileAsync(resource).ConfigureAwait(false); + using var memoryStream = new MemoryStream(); + if (inputStream is null) + { + System.Diagnostics.Trace.TraceError($"{inputStream} is null."); + return null; + } + await inputStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; + NSData? nsdata = NSData.FromStream(memoryStream); + if (nsdata is null) + { + System.Diagnostics.Trace.TraceError($"{nsdata} is null."); + return null; + } + return UIImage.LoadFromData(nsdata); + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs deleted file mode 100644 index 47180da2e7..0000000000 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Windows.Media; - -namespace CommunityToolkit.Maui.Core.Primitives; - -sealed class Metadata -{ - readonly IMediaElement? mediaElement; - readonly SystemMediaTransportControls? systemMediaControls; - readonly IDispatcher dispatcher; - /// - /// Initializes a new instance of the class. - /// - public Metadata(SystemMediaTransportControls systemMediaTransportControls, IMediaElement MediaElement, IDispatcher Dispatcher) - { - mediaElement = MediaElement; - this.dispatcher = Dispatcher; - systemMediaControls = systemMediaTransportControls; - systemMediaControls.ButtonPressed += OnSystemMediaControlsButtonPressed; - } - - - void OnSystemMediaControlsButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args) - { - if (mediaElement is null) - { - return; - } - - if (args.Button == SystemMediaTransportControlsButton.Play) - { - if (dispatcher.IsDispatchRequired) - { - dispatcher.Dispatch(() => mediaElement.Play()); - } - else - { - mediaElement.Play(); - } - } - else if (args.Button == SystemMediaTransportControlsButton.Pause) - { - if (dispatcher.IsDispatchRequired) - { - dispatcher.Dispatch(() => mediaElement.Pause()); - } - else - { - mediaElement.Pause(); - } - } - } - - /// - /// Sets the metadata for the given MediaElement. - /// - public void SetMetadata(IMediaElement mp) - { - if (systemMediaControls is null || mediaElement is null) - { - return; - } - - if (!string.IsNullOrEmpty(mp.MetadataArtworkUrl)) - { - systemMediaControls.DisplayUpdater.Thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(new Uri(mp.MetadataArtworkUrl ?? string.Empty)); - } - systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; - systemMediaControls.DisplayUpdater.MusicProperties.Artist = mp.MetadataTitle; - systemMediaControls.DisplayUpdater.MusicProperties.Title = mp.MetadataArtist; - systemMediaControls.DisplayUpdater.Update(); - } -} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index e34ef498cb..c32d46779d 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -1,9 +1,9 @@ using System.Diagnostics.CodeAnalysis; using Android.Content; +using Android.Graphics; using Android.Views; using Android.Widget; using AndroidX.Media3.Common; -using AndroidX.Media3.Common.Text; using AndroidX.Media3.Common.Util; using AndroidX.Media3.ExoPlayer; using AndroidX.Media3.Session; @@ -13,8 +13,6 @@ using CommunityToolkit.Maui.Services; using CommunityToolkit.Maui.Views; using Microsoft.Extensions.Logging; -using AudioAttributes = AndroidX.Media3.Common.AudioAttributes; -using DeviceInfo = AndroidX.Media3.Common.DeviceInfo; using MediaMetadata = AndroidX.Media3.Common.MediaMetadata; namespace CommunityToolkit.Maui.Core.Views; @@ -507,29 +505,7 @@ protected override void Dispose(bool disposing) } } - static async Task GetBytesFromMetadataArtworkUrl(string? url, CancellationToken cancellationToken = default) - { - byte[] artworkData = []; - try - { - var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false); - var stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null; - - if (stream is null) - { - return artworkData; - } - - using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); - var bytes = memoryStream.ToArray(); - return bytes; - } - catch - { - return artworkData; - } - } + [MemberNotNull(nameof(connection))] void StartService() @@ -588,7 +564,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) var path = resourceMediaSource.Path; if (!string.IsNullOrWhiteSpace(path)) { - var assetFilePath = $"asset://{package}{Path.PathSeparator}{path}"; + var assetFilePath = $"asset://{package}{System.IO.Path.PathSeparator}{path}"; return await CreateMediaItem(assetFilePath, cancellationToken).ConfigureAwait(false); } @@ -606,7 +582,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) MediaMetadata.Builder mediaMetaData = new(); mediaMetaData.SetArtist(MediaElement.MetadataArtist); mediaMetaData.SetTitle(MediaElement.MetadataTitle); - var data = await GetBytesFromMetadataArtworkUrl(MediaElement.MetadataArtworkUrl, cancellationToken).ConfigureAwait(true); + var data = await GetImageFromMediaSource(MediaElement.MetadataArtworkSource, cancellationToken).ConfigureAwait(true) ?? BlankByteArray(); mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover); mediaItem = new MediaItem.Builder(); @@ -617,20 +593,124 @@ void StopService(in BoundServiceConnection boundServiceConnection) return mediaItem; } - static class PlaybackState + static async Task GetImageFromMediaSource(MediaSource? mediaSource, CancellationToken cancellationToken = default) + { + if (mediaSource is null) + { + System.Diagnostics.Trace.TraceInformation("Atwork MediaSource is null"); + return null; + } + var artworkUrl = mediaSource; + if (artworkUrl is UriMediaSource uriMediaSource) + { + var uri = uriMediaSource.Uri; + if (string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) + { + System.Diagnostics.Trace.TraceInformation("Arkwork Uri is null or empty"); + return null; + } + using var client = new HttpClient(); + return await client.GetByteArrayAsync(uri, cancellationToken).ConfigureAwait(false); + } + else if (artworkUrl is FileMediaSource fileMediaSource) + { + var filePath = fileMediaSource.Path; + if(string.IsNullOrWhiteSpace(filePath)) + { + System.Diagnostics.Trace.TraceInformation("Arkwork File path is null or empty"); + return null; + } + return await GetByteArrayFromFile(filePath, cancellationToken).ConfigureAwait(false); + } + else if (artworkUrl is ResourceMediaSource resourceMediaSource) + { + var path = resourceMediaSource.Path; + var item = path?[(path.LastIndexOf('/') + 1)..]; + if (string.IsNullOrWhiteSpace(item)) + { + System.Diagnostics.Trace.TraceInformation("Arkwork Resource path is null or empty"); + return null; + } + return await GetByteArrayFromResource(item).ConfigureAwait(false); + } + return null; + } + + static async Task GetBytesFromMetadataArtworkUrl(string? url, CancellationToken cancellationToken = default) + { + var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false); + var stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null; + + if (stream is null) + { + System.Diagnostics.Trace.TraceInformation("Arkwork Url stream is null"); + return null; + } + + using var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); + var bytes = memoryStream.ToArray(); + return bytes; + } + + static async Task GetByteArrayFromResource(string resource) + { + // Android file system does not report size of file so we need to use a buffer for the stream. + using var stream = await FileSystem.Current.OpenAppPackageFileAsync(resource); + if(stream is null) + { + System.Diagnostics.Trace.TraceInformation("Arkwork Resource stream is null"); + return null; + } + using var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + var bytes = memoryStream.ToArray(); + return bytes; + } + + static async Task GetByteArrayFromFile(string filePath, CancellationToken cancellationToken = default) { - public const int StateBuffering = 6; - public const int StateConnecting = 8; - public const int StateFailed = 7; - public const int StateFastForwarding = 4; - public const int StateNone = 0; - public const int StatePaused = 2; - public const int StatePlaying = 3; - public const int StateRewinding = 5; - public const int StateSkippingToNext = 10; - public const int StateSkippingToPrevious = 9; - public const int StateSkippingToQueueItem = 11; - public const int StateStopped = 1; - public const int StateError = 7; + if(!File.Exists(filePath)) + { + System.Diagnostics.Trace.TraceInformation("Arkwork File does not exist"); + return null; + } + var stream = File.OpenRead(filePath); + var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); + var bytes = memoryStream.ToArray(); + return bytes; } + static byte[] BlankByteArray() + { + var bitmapConfig = Bitmap.Config.Argb8888 ?? throw new InvalidOperationException("Bitmap config cannot be null"); + var bitmap = Bitmap.CreateBitmap(1024, 768, bitmapConfig, true); + + Canvas canvas = new(); + canvas.SetBitmap(bitmap); + canvas.DrawColor(Android.Graphics.Color.Transparent); + canvas.Save(); + + MemoryStream stream = new(); + var format = Bitmap.CompressFormat.Png ?? throw new InvalidOperationException("Bitmap format cannot be null"); + bitmap.Compress(format, 100, stream); + stream.Position = 0; + return stream.ToArray(); + } +} +static class PlaybackState +{ + public const int StateBuffering = 6; + public const int StateConnecting = 8; + public const int StateFailed = 7; + public const int StateFastForwarding = 4; + public const int StateNone = 0; + public const int StatePaused = 2; + public const int StatePlaying = 3; + public const int StateRewinding = 5; + public const int StateSkippingToNext = 10; + public const int StateSkippingToPrevious = 9; + public const int StateSkippingToQueueItem = 11; + public const int StateStopped = 1; + public const int StateError = 7; } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index a97224028b..4c594c04b9 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -211,14 +211,14 @@ protected virtual partial void PlatformUpdateAspect() }; } - protected virtual partial ValueTask PlatformUpdateSource() + protected virtual async partial ValueTask PlatformUpdateSource() { MediaElement.CurrentStateChanged(MediaElementState.Opening); AVAsset? asset = null; if (Player is null) { - return ValueTask.CompletedTask; + return; } metaData ??= new(Player); @@ -266,7 +266,7 @@ protected virtual partial ValueTask PlatformUpdateSource() ? new AVPlayerItem(asset) : null; - metaData.SetMetadata(PlayerItem, MediaElement); + await metaData.SetMetadata(PlayerItem, MediaElement); CurrentItemErrorObserver?.Dispose(); Player.ReplaceCurrentItemWithPlayerItem(PlayerItem); @@ -298,7 +298,7 @@ protected virtual partial ValueTask PlatformUpdateSource() { Player.Play(); } - SetPoster(); + await SetPoster(); } else if (PlayerItem is null) { @@ -306,50 +306,6 @@ protected virtual partial ValueTask PlatformUpdateSource() MediaElement.CurrentStateChanged(MediaElementState.None); } - - return ValueTask.CompletedTask; - } - - void SetPoster() - { - if (PlayerItem is null || metaData is null) - { - return; - } - var videoTrack = PlayerItem.Asset.TracksWithMediaType(AVMediaTypes.Video.GetConstant()).FirstOrDefault(); - if (videoTrack is not null) - { - return; - } - if (PlayerItem.Asset.Tracks.Length == 0) - { - // No video track found and no tracks found. This is likely an audio file. So we can't set a poster. - return; - } - - if (PlayerViewController?.View is not null && PlayerViewController.ContentOverlayView is not null && !string.IsNullOrEmpty(MediaElement.MetadataArtworkUrl)) - { - var image = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(MediaElement.MetadataArtworkUrl))) ?? new UIImage(); - var imageView = new UIImageView(image) - { - ContentMode = UIViewContentMode.ScaleAspectFit, - TranslatesAutoresizingMaskIntoConstraints = false, - ClipsToBounds = true, - AutoresizingMask = UIViewAutoresizing.FlexibleDimensions - }; - - PlayerViewController.ContentOverlayView.AddSubview(imageView); - NSLayoutConstraint.ActivateConstraints( - [ - imageView.CenterXAnchor.ConstraintEqualTo(PlayerViewController.ContentOverlayView.CenterXAnchor), - imageView.CenterYAnchor.ConstraintEqualTo(PlayerViewController.ContentOverlayView.CenterYAnchor), - imageView.WidthAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.WidthAnchor), - imageView.HeightAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.HeightAnchor), - - // Maintain the aspect ratio - imageView.WidthAnchor.ConstraintEqualTo(imageView.HeightAnchor, image.Size.Width / image.Size.Height) - ]); - } } protected virtual partial void PlatformUpdateSpeed() @@ -541,6 +497,51 @@ protected virtual void Dispose(bool disposing) } } + async Task SetPoster(CancellationToken cancellationToken = default) + { + if (PlayerItem is null || metaData is null || MediaElement is null) + { + return; + } + var artwork = await Metadata.MetadataArtworkUrl(MediaElement.MetadataArtworkSource, cancellationToken).ConfigureAwait(false); + if (artwork is null) + { + System.Diagnostics.Trace.TraceError($"{artwork} is null."); + return; + } + var videoTrack = PlayerItem.Asset.TracksWithMediaType(AVMediaTypes.Video.GetConstant()).FirstOrDefault(); + if (videoTrack is not null) + { + return; + } + if (PlayerItem.Asset.Tracks.Length == 0) + { + // No video track found and no tracks found. This is likely an audio file. So we can't set a poster. + return; + } + if (PlayerViewController?.View is not null && PlayerViewController.ContentOverlayView is not null) + { + var imageView = new UIImageView(artwork) + { + ContentMode = UIViewContentMode.ScaleAspectFit, + TranslatesAutoresizingMaskIntoConstraints = false, + ClipsToBounds = true, + AutoresizingMask = UIViewAutoresizing.FlexibleDimensions + }; + + PlayerViewController.ContentOverlayView.AddSubview(imageView); + NSLayoutConstraint.ActivateConstraints( + [ + imageView.CenterXAnchor.ConstraintEqualTo(PlayerViewController.ContentOverlayView.CenterXAnchor), + imageView.CenterYAnchor.ConstraintEqualTo(PlayerViewController.ContentOverlayView.CenterYAnchor), + imageView.WidthAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.WidthAnchor), + imageView.HeightAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.HeightAnchor), + + // Maintain the aspect ratio + imageView.WidthAnchor.ConstraintEqualTo(imageView.HeightAnchor, artwork.Size.Width / artwork.Size.Height) + ]); + } + } void AddStatusObservers() { diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index 485314cec3..200d8444e7 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Numerics; using CommunityToolkit.Maui.Core.Primitives; using CommunityToolkit.Maui.Views; @@ -8,6 +7,7 @@ using Windows.Media; using Windows.Media.Playback; using Windows.Storage; +using Windows.Storage.Streams; using Windows.System.Display; using ParentWindow = CommunityToolkit.Maui.Extensions.PageExtensions.ParentWindow; using WindowsMediaElement = Windows.Media.Playback.MediaPlayer; @@ -17,7 +17,6 @@ namespace CommunityToolkit.Maui.Core.Views; partial class MediaManager : IDisposable { - Metadata? metadata; SystemMediaTransportControls? systemMediaControls; // States that allow changing position @@ -59,7 +58,7 @@ public PlatformMediaElement CreatePlatformView() Player.MediaPlayer.MediaEnded += OnMediaElementMediaEnded; Player.MediaPlayer.VolumeChanged += OnMediaElementVolumeChanged; Player.MediaPlayer.IsMutedChanged += OnMediaElementIsMutedChanged; - + systemMediaControls = Player.MediaPlayer.SystemMediaTransportControls; return Player; } @@ -358,38 +357,49 @@ static bool IsZero(TValue numericValue) where TValue : INumber async ValueTask UpdateMetadata() { - if (systemMediaControls is null || Player is null) + if (systemMediaControls is null || Player is null || MediaElement.MetadataArtworkSource is null) { return; } - metadata ??= new(systemMediaControls, MediaElement, Dispatcher); - metadata.SetMetadata(MediaElement); - if (string.IsNullOrEmpty(MediaElement.MetadataArtworkUrl)) + var artwork = ArtworkUrl(MediaElement.MetadataArtworkSource); + if(string.IsNullOrEmpty(artwork)) { return; } - if (!Uri.TryCreate(MediaElement.MetadataArtworkUrl, UriKind.RelativeOrAbsolute, out var metadataArtworkUri)) + + var file = RandomAccessStreamReference.CreateFromUri(new Uri(artwork)); + if (file is not null) { - Trace.TraceError($"{nameof(MediaElement)} unable to update artwork because {nameof(MediaElement.MetadataArtworkUrl)} is not a valid URI"); - return; + systemMediaControls.DisplayUpdater.Thumbnail = file; + systemMediaControls.DisplayUpdater.Update(); + Uri uri = new(artwork); + Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); } - - if (Dispatcher.IsDispatchRequired) + + if (File.Exists(artwork)) { - await Dispatcher.DispatchAsync(() => UpdatePosterSource(Player, metadataArtworkUri)); - } - else - { - UpdatePosterSource(Player, metadataArtworkUri); + StorageFile ImageFile = await StorageFile.GetFileFromPathAsync(artwork); + Dispatcher.Dispatch(async () => + { + var bitmap = await LoadBitmapImageAsync(ImageFile); + Player.PosterSource = bitmap; + }); + systemMediaControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromFile(ImageFile); } - static void UpdatePosterSource(in MediaPlayerElement player, in Uri metadataArtworkUri) - { - player.PosterSource = new BitmapImage(metadataArtworkUri); - } + systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; + systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; + systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; + systemMediaControls.DisplayUpdater.Update(); + } + static async Task LoadBitmapImageAsync(StorageFile artwork) + { + var bitmap = new BitmapImage(); + using var randomAccessStream = await artwork.OpenReadAsync(); + await bitmap.SetSourceAsync(randomAccessStream); + return bitmap; } - async void OnMediaElementMediaOpened(WindowsMediaElement sender, object args) { if (Player is null) @@ -505,6 +515,23 @@ void OnPlaybackSessionPlaybackStateChanged(MediaPlaybackSession sender, object a } } + static string ArtworkUrl(MediaSource? artwork) + { + if (artwork is UriMediaSource uriMediaSource) + { + return uriMediaSource.Uri?.AbsoluteUri ?? string.Empty; + } + else if (artwork is FileMediaSource fileMediaSource) + { + return fileMediaSource.Path ?? string.Empty; + } + else if (artwork is ResourceMediaSource resourceMediaSource) + { + return "ms-appx:///" + resourceMediaSource.Path; + } + return string.Empty; + } + void OnPlaybackSessionSeekCompleted(MediaPlaybackSession sender, object args) { MediaElement?.SeekCompleted(); diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs index 9f0e620de1..ca7fbafc38 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs @@ -16,17 +16,17 @@ public MediaElementTests() public void PosterIsNotStringEmptyOrNull() { MediaElement mediaElement = new(); - mediaElement.MetadataArtworkUrl = "https://www.example.com/image.jpg"; - Assert.False(string.IsNullOrEmpty(mediaElement.MetadataArtworkUrl)); + mediaElement.MetadataArtworkSource = "https://www.example.com/image.jpg"; + Assert.IsType(mediaElement.MetadataArtworkSource, exactMatch: false); + Assert.False((mediaElement.MetadataArtworkSource) is null); } [Fact] public void PosterIsStringEmptyDoesNotThrow() { MediaElement mediaElement = new(); - mediaElement.MetadataArtworkUrl = string.Empty; - Assert.True(string.IsNullOrEmpty(mediaElement.MetadataArtworkUrl)); - Assert.True(mediaElement.MetadataArtworkUrl == string.Empty); + mediaElement.MetadataArtworkSource = null; + Assert.True((mediaElement.MetadataArtworkSource) is null); } [Fact] From 9c3ca06885c9be0cf8164f09ecf79ecdd16b5713 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Sun, 9 Feb 2025 21:41:10 -0800 Subject: [PATCH 02/44] Refactor and clean up media manager methods Simplified `SetMetadata` in `Metadata.macios.cs` by removing a null check for `artwork` before checking if it is a `UIImage`. Cleaned up `Dispose` in `MediaManager.android.cs` by removing redundant empty lines. Streamlined `StopService` in `MediaManager.android.cs` by removing `HttpClient` usage and `GetBytesFromMetadataArtworkUrl` method. Updated `Dispose` in `MediaManager.macios.cs` to change `SetPoster` return type from `Task` to `ValueTask`. Refactored `UpdateMetadata` in `MediaManager.windows.cs` to handle different `MetadataArtworkSource` types explicitly and removed redundant `ArtworkUrl` method. Cleaned up `OnPlaybackSessionPlaybackStateChanged` in `MediaManager.windows.cs` by removing the now redundant `ArtworkUrl` method. --- .../Primitives/Metadata.macios.cs | 2 +- .../Views/MediaManager.android.cs | 22 ------ .../Views/MediaManager.macios.cs | 2 +- .../Views/MediaManager.windows.cs | 68 +++++++++---------- 4 files changed, 35 insertions(+), 59 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index f58458578c..62fd178753 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -79,7 +79,7 @@ public async Task SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElem ClearNowPlaying(); var artwork = await MetadataArtworkUrl(mediaElement.MetadataArtworkSource).ConfigureAwait(false); - if (artwork is not null && artwork is UIImage image) + if (artwork is UIImage image) { NowPlayingInfo.Artwork = new(boundsSize: new(320, 240), requestHandler: _ => image); } diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index c32d46779d..32cf79eac6 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -505,8 +505,6 @@ protected override void Dispose(bool disposing) } } - - [MemberNotNull(nameof(connection))] void StartService() { @@ -597,7 +595,6 @@ void StopService(in BoundServiceConnection boundServiceConnection) { if (mediaSource is null) { - System.Diagnostics.Trace.TraceInformation("Atwork MediaSource is null"); return null; } var artworkUrl = mediaSource; @@ -609,7 +606,6 @@ void StopService(in BoundServiceConnection boundServiceConnection) System.Diagnostics.Trace.TraceInformation("Arkwork Uri is null or empty"); return null; } - using var client = new HttpClient(); return await client.GetByteArrayAsync(uri, cancellationToken).ConfigureAwait(false); } else if (artworkUrl is FileMediaSource fileMediaSource) @@ -635,23 +631,6 @@ void StopService(in BoundServiceConnection boundServiceConnection) } return null; } - - static async Task GetBytesFromMetadataArtworkUrl(string? url, CancellationToken cancellationToken = default) - { - var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false); - var stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null; - - if (stream is null) - { - System.Diagnostics.Trace.TraceInformation("Arkwork Url stream is null"); - return null; - } - - using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); - var bytes = memoryStream.ToArray(); - return bytes; - } static async Task GetByteArrayFromResource(string resource) { @@ -659,7 +638,6 @@ void StopService(in BoundServiceConnection boundServiceConnection) using var stream = await FileSystem.Current.OpenAppPackageFileAsync(resource); if(stream is null) { - System.Diagnostics.Trace.TraceInformation("Arkwork Resource stream is null"); return null; } using var memoryStream = new MemoryStream(); diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index 4c594c04b9..a09b64dc32 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -497,7 +497,7 @@ protected virtual void Dispose(bool disposing) } } - async Task SetPoster(CancellationToken cancellationToken = default) + async ValueTask SetPoster(CancellationToken cancellationToken = default) { if (PlayerItem is null || metaData is null || MediaElement is null) { diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index 200d8444e7..5d0e0019b1 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -362,30 +362,45 @@ async ValueTask UpdateMetadata() return; } - var artwork = ArtworkUrl(MediaElement.MetadataArtworkSource); - if(string.IsNullOrEmpty(artwork)) + if (MediaElement.MetadataArtworkSource is UriMediaSource uriMediaSource) { - return; + var artwork = uriMediaSource.Uri?.AbsoluteUri ?? string.Empty; + var file = RandomAccessStreamReference.CreateFromUri(new Uri(artwork)); + if (file is not null) + { + systemMediaControls.DisplayUpdater.Thumbnail = file; + systemMediaControls.DisplayUpdater.Update(); + Uri uri = new(artwork); + Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); + } } - - var file = RandomAccessStreamReference.CreateFromUri(new Uri(artwork)); - if (file is not null) + + if (MediaElement.MetadataArtworkSource is FileMediaSource fileMediaSource) { - systemMediaControls.DisplayUpdater.Thumbnail = file; - systemMediaControls.DisplayUpdater.Update(); - Uri uri = new(artwork); - Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); + var artwork = fileMediaSource.Path; + if (File.Exists(artwork)) + { + StorageFile ImageFile = await StorageFile.GetFileFromPathAsync(artwork); + Dispatcher.Dispatch(async () => + { + var bitmap = await LoadBitmapImageAsync(ImageFile); + Player.PosterSource = bitmap; + }); + systemMediaControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromFile(ImageFile); + + } } - - if (File.Exists(artwork)) + if (MediaElement.MetadataArtworkSource is ResourceMediaSource resourceMediaSource) { - StorageFile ImageFile = await StorageFile.GetFileFromPathAsync(artwork); - Dispatcher.Dispatch(async () => + var artwork = "ms-appx:///" + resourceMediaSource.Path; + var file = RandomAccessStreamReference.CreateFromUri(new Uri(artwork)); + if (file is not null) { - var bitmap = await LoadBitmapImageAsync(ImageFile); - Player.PosterSource = bitmap; - }); - systemMediaControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromFile(ImageFile); + systemMediaControls.DisplayUpdater.Thumbnail = file; + systemMediaControls.DisplayUpdater.Update(); + Uri uri = new(artwork); + Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); + } } systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; @@ -514,23 +529,6 @@ void OnPlaybackSessionPlaybackStateChanged(MediaPlaybackSession sender, object a }); } } - - static string ArtworkUrl(MediaSource? artwork) - { - if (artwork is UriMediaSource uriMediaSource) - { - return uriMediaSource.Uri?.AbsoluteUri ?? string.Empty; - } - else if (artwork is FileMediaSource fileMediaSource) - { - return fileMediaSource.Path ?? string.Empty; - } - else if (artwork is ResourceMediaSource resourceMediaSource) - { - return "ms-appx:///" + resourceMediaSource.Path; - } - return string.Empty; - } void OnPlaybackSessionSeekCompleted(MediaPlaybackSession sender, object args) { From 49ad676c5f78b428717c0a228844261ec5241644 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Mon, 10 Feb 2025 01:25:55 -0800 Subject: [PATCH 03/44] Fix incorrect logging --- .../Primitives/Metadata.macios.cs | 4 ++-- .../Views/MediaManager.macios.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index 62fd178753..79312339dc 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -225,7 +225,7 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) using var memoryStream = new MemoryStream(); if (inputStream is null) { - System.Diagnostics.Trace.TraceError($"{inputStream} is null."); + System.Diagnostics.Trace.TraceInformation($"{inputStream} is null."); return null; } await inputStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); @@ -233,7 +233,7 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) NSData? nsdata = NSData.FromStream(memoryStream); if (nsdata is null) { - System.Diagnostics.Trace.TraceError($"{nsdata} is null."); + System.Diagnostics.Trace.TraceInformation($"{nsdata} is null."); return null; } return UIImage.LoadFromData(nsdata); diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index 9d70ad4d75..d687d561b9 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -506,7 +506,7 @@ async ValueTask SetPoster(CancellationToken cancellationToken = default) var artwork = await Metadata.MetadataArtworkUrl(MediaElement.MetadataArtworkSource, cancellationToken).ConfigureAwait(false); if (artwork is null) { - System.Diagnostics.Trace.TraceError($"{artwork} is null."); + System.Diagnostics.Trace.TraceInformation($"{artwork} is null."); return; } var videoTrack = PlayerItem.Asset.TracksWithMediaType(AVMediaTypes.Video.GetConstant()).FirstOrDefault(); From 193ac8a0877f2e5cf86a9bcf5ffe480ff39f1dc9 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 12 Feb 2025 02:19:11 -0800 Subject: [PATCH 04/44] Fix incorrect accesibility modifier --- .../Interfaces/IMediaElement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs b/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs index c46c813f38..03deecc2f5 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs @@ -21,7 +21,7 @@ public interface IMediaElement : IView, IAsynchronousMediaElementHandler /// /// Gets or sets the artwork Image source. /// - public MediaSource? MetadataArtworkSource { get; set; } + MediaSource? MetadataArtworkSource { get; set; } /// /// Gets the media aspect ratio. From 81ad1a841f314b62ff56f341b655c15f0c65871c Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Sun, 4 May 2025 16:13:30 -0700 Subject: [PATCH 05/44] Revert merge error in file that was not supposed to be changed --- .../Pages/Converters/InvertedBoolConverterPage.xaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Converters/InvertedBoolConverterPage.xaml b/samples/CommunityToolkit.Maui.Sample/Pages/Converters/InvertedBoolConverterPage.xaml index 29fea276d3..8bac8c620e 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Converters/InvertedBoolConverterPage.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Converters/InvertedBoolConverterPage.xaml @@ -28,8 +28,6 @@ HorizontalOptions="CenterAndExpand" Spacing="10"> - string MetadataArtist { get; set; } - /// - /// Gets or sets the artwork Image Url. - /// - string MetadataArtworkUrl { get; set; } - /// /// Occurs when the media has ended playing successfully. /// diff --git a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs index 426c78dd9b..e6771176cb 100644 --- a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs +++ b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs @@ -190,10 +190,11 @@ internal event EventHandler StopRequested public partial string MetadataArtist { get; set; } = MediaElementDefaults.MetadataArtist; /// - /// Gets or sets the of the media. + /// Gets or sets the of the media. /// [BindableProperty] - public partial string MetadataArtworkUrl { get; set; } = MediaElementDefaults.MetadataArtworkUrl; + [TypeConverter(typeof(MediaSourceConverter))] + public partial MediaSource? MetadataArtworkSource { get; set; } = MediaElementDefaults.MetadataArtworkSource; /// /// Gets or sets the of the media. diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs index a1105823a8..fc1384b173 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs @@ -1,3 +1,5 @@ +using CommunityToolkit.Maui.Views; + namespace CommunityToolkit.Maui.Core; static class MediaElementDefaults @@ -26,7 +28,7 @@ static class MediaElementDefaults public const string MetadataArtist = ""; - public const string MetadataArtworkUrl = ""; + public static MediaSource? MetadataArtworkSource { get; } = null; public const MediaElementState CurrentState = MediaElementState.None; diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs index 9b71a68ee9..59a4a01ba4 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs @@ -1,4 +1,5 @@ -using Microsoft.Maui.Dispatching; +using CommunityToolkit.Maui.Core.Views; +using Microsoft.Maui.Dispatching; using Windows.Media; namespace CommunityToolkit.Maui.Core.Primitives; @@ -54,17 +55,18 @@ void OnSystemMediaControlsButtonPressed(SystemMediaTransportControls sender, Sys /// /// Sets the metadata for the given MediaElement. /// - public void SetMetadata(IMediaElement mp) + public void SetMetadata(IMediaElement mp, MediaManager mediaManager) { - if (systemMediaControls is null || mediaElement is null) + if (systemMediaControls is null || mediaElement is null || mp is null) { return; } - - if (!string.IsNullOrEmpty(mp.MetadataArtworkUrl)) + var source = mediaManager.GetSource(mp.MetadataArtworkSource); + if (!string.IsNullOrEmpty(source)) { - systemMediaControls.DisplayUpdater.Thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(new Uri(mp.MetadataArtworkUrl ?? string.Empty)); + systemMediaControls.DisplayUpdater.Thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(new Uri(source)); } + systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; systemMediaControls.DisplayUpdater.MusicProperties.Artist = mp.MetadataTitle; systemMediaControls.DisplayUpdater.MusicProperties.Title = mp.MetadataArtist; diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index e6005b9125..39c1bcc9dd 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -223,44 +223,11 @@ protected virtual async partial ValueTask PlatformUpdateSource() metaData ??= new(Player); Metadata.ClearNowPlaying(); PlayerViewController?.ContentOverlayView?.Subviews.FirstOrDefault()?.RemoveFromSuperview(); - - if (MediaElement.Source is UriMediaSource uriMediaSource) - { - var uri = uriMediaSource.Uri; - if (!string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) - { - asset = AVAsset.FromUrl(new NSUrl(uri.AbsoluteUri)); - } - } - else if (MediaElement.Source is FileMediaSource fileMediaSource) + var source = GetSource(MediaElement.Source); + if (!string.IsNullOrWhiteSpace(source)) { - var uri = fileMediaSource.Path; - - if (!string.IsNullOrWhiteSpace(uri)) - { - asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(uri)); - } - } - else if (MediaElement.Source is ResourceMediaSource resourceMediaSource) - { - var path = resourceMediaSource.Path; - - if (!string.IsNullOrWhiteSpace(path) && Path.HasExtension(path)) - { - string directory = Path.GetDirectoryName(path) ?? ""; - string filename = Path.GetFileNameWithoutExtension(path); - string extension = Path.GetExtension(path)[1..]; - var url = NSBundle.MainBundle.GetUrlForResource(filename, - extension, directory); - - asset = AVAsset.FromUrl(url); - } - else - { - Logger.LogWarning("Invalid file path for ResourceMediaSource."); - } + asset = AVAsset.FromUrl(new NSUrl(source)); } - PlayerItem = asset is not null ? new AVPlayerItem(asset) : null; @@ -459,6 +426,48 @@ protected virtual void Dispose(bool disposing) static TimeSpan ConvertTime(CMTime cmTime) => TimeSpan.FromSeconds(double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds); + string? GetSource(MediaSource? source) + { + if (source is UriMediaSource uriMediaSource) + { + var uri = uriMediaSource.Uri; + if (!string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) + { + return uri.AbsoluteUri; + } + } + else if (source is FileMediaSource fileMediaSource) + { + var uri = fileMediaSource.Path; + + if (!string.IsNullOrWhiteSpace(uri)) + { + return uri; + } + } + else if (source is ResourceMediaSource resourceMediaSource) + { + var path = resourceMediaSource.Path; + + if (!string.IsNullOrWhiteSpace(path) && Path.HasExtension(path)) + { + string directory = Path.GetDirectoryName(path) ?? ""; + string filename = Path.GetFileNameWithoutExtension(path); + string extension = Path.GetExtension(path)[1..]; + var url = NSBundle.MainBundle.GetUrlForResource(filename, + extension, directory); + if (string.IsNullOrEmpty(url.AbsoluteString)) + { + return url.AbsoluteString; + } + } + else + { + Logger.LogWarning("Invalid file path for ResourceMediaSource."); + } + } + return null; + } static async Task<(int Width, int Height)> GetVideoDimensions(AVPlayerItem avPlayerItem) { // Create an AVAsset instance with the video file URL @@ -497,55 +506,8 @@ protected virtual void Dispose(bool disposing) return asset.TracksWithMediaType(AVMediaTypes.Video.GetConstant() ?? "0").FirstOrDefault(); } - // If all else fails, just return 0, 0 - return (0, 0); - } - } - - async ValueTask SetPoster(CancellationToken cancellationToken = default) - { - if (PlayerItem is null || metaData is null || MediaElement is null) - { - return; - } - var artwork = await Metadata.MetadataArtworkUrl(MediaElement.MetadataArtworkSource, cancellationToken).ConfigureAwait(false); - if (artwork is null) - { - System.Diagnostics.Trace.TraceInformation($"{artwork} is null."); - return; - } - var videoTrack = PlayerItem.Asset.TracksWithMediaType(AVMediaTypes.Video.GetConstant()).FirstOrDefault(); - if (videoTrack is not null) - { - return; - } - if (PlayerItem.Asset.Tracks.Length == 0) - { - // No video track found and no tracks found. This is likely an audio file. So we can't set a poster. - return; - } - if (PlayerViewController?.View is not null && PlayerViewController.ContentOverlayView is not null) - { - var imageView = new UIImageView(artwork) - { - ContentMode = UIViewContentMode.ScaleAspectFit, - TranslatesAutoresizingMaskIntoConstraints = false, - ClipsToBounds = true, - AutoresizingMask = UIViewAutoresizing.FlexibleDimensions - }; - - PlayerViewController.ContentOverlayView.AddSubview(imageView); - NSLayoutConstraint.ActivateConstraints( - [ - imageView.CenterXAnchor.ConstraintEqualTo(PlayerViewController.ContentOverlayView.CenterXAnchor), - imageView.CenterYAnchor.ConstraintEqualTo(PlayerViewController.ContentOverlayView.CenterYAnchor), - imageView.WidthAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.WidthAnchor), - imageView.HeightAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.HeightAnchor), - - // Maintain the aspect ratio - imageView.WidthAnchor.ConstraintEqualTo(imageView.HeightAnchor, artwork.Size.Width / artwork.Size.Height) - ]); - } + // On iOS 18+ and MacCatalyst 18+, use the new API or return null if not available + return null; } void AddStatusObservers() @@ -569,21 +531,47 @@ async Task SetPoster() return; } - var videoTrack = await GetTrack(PlayerItem.Asset); + AVAssetTrack? videoTrack = null; + if (PlayerItem.Asset is not null) + { +#if IOS || MACCATALYST + // Use the non-obsolete API for iOS 18+ and MacCatalyst 18+ + if (OperatingSystem.IsIOSVersionAtLeast(18) || OperatingSystem.IsMacCatalystVersionAtLeast(18)) + { + // On iOS 18+ and MacCatalyst 18+, AVAsset.TracksWithMediaType is obsolete. + // Instead, use the Tracks property and filter for video tracks. + videoTrack = PlayerItem.Asset.Tracks.FirstOrDefault(t => t.MediaType == AVMediaTypes.Video.GetConstant()); + } + else +#endif + { + // For earlier versions, use the existing API, but check for null + var videoMediaType = AVMediaTypes.Video.GetConstant(); + if (videoMediaType is not null) + { + videoTrack = PlayerItem.Asset.TracksWithMediaType(videoMediaType).FirstOrDefault(); + } + } + } if (videoTrack is not null) { return; } - if (PlayerItem.Asset.Tracks.Length == 0) + if (PlayerItem.Asset?.Tracks.Length == 0) { // No video track found and no tracks found. This is likely an audio file. So we can't set a poster. return; } - if (PlayerViewController?.View is not null && PlayerViewController.ContentOverlayView is not null && !string.IsNullOrEmpty(MediaElement.MetadataArtworkUrl)) + if (PlayerViewController?.View is not null && PlayerViewController.ContentOverlayView is not null) { - var image = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(MediaElement.MetadataArtworkUrl))) ?? new UIImage(); + var source = GetSource(MediaElement.MetadataArtworkSource); + if (string.IsNullOrWhiteSpace(source)) + { + return; + } + var image = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(source))) ?? new UIImage(); var imageView = new UIImageView(image) { ContentMode = UIViewContentMode.ScaleAspectFit, @@ -678,7 +666,6 @@ void StatusChanged(NSObservedChange obj) MediaElement.CurrentStateChanged(newState); } - void TimeControlStatusChanged(NSObservedChange obj) { if (Player is null || Player.Status is AVPlayerStatus.Unknown diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index 9e1893c45d..fc51d1dd3f 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -1,4 +1,5 @@ using System.Numerics; +using CommunityToolkit.Maui.Extensions; using CommunityToolkit.Maui.Views; using Microsoft.Extensions.Logging; using Microsoft.Maui; @@ -283,39 +284,75 @@ protected virtual async partial ValueTask PlatformUpdateSource() MediaElement.Duration = TimeSpan.Zero; Player.AutoPlay = MediaElement.ShouldAutoPlay; - if (MediaElement.Source is UriMediaSource uriMediaSource) + var source = GetSource(MediaElement.Source); + + if (MediaElement.Source is UriMediaSource) + { + Player.MediaPlayer.SetUriSource(new Uri(source)); + } + else if (MediaElement.Source is FileMediaSource) + { + StorageFile storageFile = await StorageFile.GetFileFromPathAsync(source); + Player.MediaPlayer.SetFileSource(storageFile); + } + else if (MediaElement.Source is ResourceMediaSource) + { + string path = GetFullAppPackageFilePath(source); + if (!string.IsNullOrWhiteSpace(path)) + { + Player.MediaPlayer.SetUriSource(new Uri(path)); + } + } + } + /// + /// Gets the string representation of the specified media source, such as a URI, file path, or resource path. + /// + /// The returned value depends on the concrete type of the provided media source. For a URI media + /// source, the absolute URI is returned. For a file media source, the file path is returned. For a resource media + /// source, the full application package file path is returned if available. If the source does not contain a valid + /// path or is not recognized, an empty string is returned. + /// The media source to retrieve the string representation for. Can be a URI, file, or resource media source. If null, + /// an empty string is returned. + /// A string containing the URI, file path, or resource path of the media source. Returns an empty string if the source + /// is null or does not contain a valid path. + public string GetSource(MediaSource? source) + { + if (source == null) + { + return string.Empty; + } + if (source is UriMediaSource uriMediaSource) { var uri = uriMediaSource.Uri?.AbsoluteUri; if (!string.IsNullOrWhiteSpace(uri)) { - Player.MediaPlayer.SetUriSource(new Uri(uri)); + return uri; } } - else if (MediaElement.Source is FileMediaSource fileMediaSource) + else if (source is FileMediaSource fileMediaSource) { var filename = fileMediaSource.Path; if (!string.IsNullOrWhiteSpace(filename)) { - StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename); - Player.MediaPlayer.SetFileSource(storageFile); + return filename; } } - else if (MediaElement.Source is ResourceMediaSource resourceMediaSource) + else if (source is ResourceMediaSource resourceMediaSource) { if (string.IsNullOrWhiteSpace(resourceMediaSource.Path)) { Logger.LogInformation("ResourceMediaSource Path is null or empty"); - return; + return string.Empty; } string path = GetFullAppPackageFilePath(resourceMediaSource.Path); if (!string.IsNullOrWhiteSpace(path)) { - Player.MediaPlayer.SetUriSource(new Uri(path)); + return path; } } + return string.Empty; } - protected virtual partial void PlatformUpdateShouldLoopPlayback() { if (Player is null) diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs index 946aa91cdd..89af82f43a 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaElementTests.cs @@ -17,7 +17,7 @@ public void VerifyDefaults() { // Arrange MediaElement mediaElement = new(); - + // Act // Assert @@ -27,7 +27,7 @@ public void VerifyDefaults() Assert.Equal(MediaElementDefaults.Duration, mediaElement.Duration); Assert.Equal(MediaElementDefaults.MediaWidth, mediaElement.MediaWidth); Assert.Equal(MediaElementDefaults.MetadataArtist, mediaElement.MetadataArtist); - Assert.Equal(MediaElementDefaults.MetadataArtworkUrl, mediaElement.MetadataArtworkUrl); + Assert.Equal(MediaElementDefaults.MetadataArtworkSource, mediaElement.MetadataArtworkSource); Assert.Equal(MediaElementDefaults.Position, mediaElement.Position); Assert.Equal(MediaElementDefaults.ShouldAutoPlay, mediaElement.ShouldAutoPlay); Assert.Equal(MediaElementDefaults.ShouldKeepScreenOn, mediaElement.ShouldKeepScreenOn); @@ -38,7 +38,7 @@ public void VerifyDefaults() Assert.Equal(MediaElementDefaults.Volume, mediaElement.Volume); Assert.Equal(MediaElementDefaults.MetadataTitle, mediaElement.MetadataTitle); } - + [Fact] public void PosterIsNotStringEmptyOrNull() { From a83e7f87caaee4318b6b70784aa8ef774248648f Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 20:12:43 -0800 Subject: [PATCH 09/44] Add file picker support and improve metadata handling Added "Load from File" option to MediaElementPage for local file playback. Refactored metadata artwork handling for Android and Windows to support file, resource, and URI sources. Unified MediaItem metadata creation on Android and improved artwork path resolution on Windows. Removed obsolete Metadata.windows.cs and integrated its logic. Includes minor bug fixes and code cleanups. --- .../MediaElement/MediaElementPage.xaml.cs | 37 +- .../Primitives/Metadata.windows.cs | 75 ---- .../Views/MediaManager.android.cs | 323 ++++++++---------- .../Views/MediaManager.windows.cs | 108 +++--- 4 files changed, 233 insertions(+), 310 deletions(-) delete mode 100644 src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index c958c9dc3a..9298bf90a2 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -15,6 +15,7 @@ public partial class MediaElementPage : BasePage const string loadLocalResource = "Load Local Resource"; const string resetSource = "Reset Source to null"; const string loadMusic = "Load Music"; + const string loadFromFile = "Load from File"; const string botImageUrl = "https://lh3.googleusercontent.com/pw/AP1GczNRrebWCJvfdIau1EbsyyYiwAfwHS0JXjbioXvHqEwYIIdCzuLodQCZmA57GADIo5iB3yMMx3t_vsefbfoHwSg0jfUjIXaI83xpiih6d-oT7qD_slR0VgNtfAwJhDBU09kS5V2T5ZML-WWZn8IrjD4J-g=w1792-h1024-s-no-gm"; const string hlsStreamTestUrl = "https://mtoczko.github.io/hls-test-streams/test-gap/playlist.m3u8"; const string hal9000AudioUrl = "https://github.com/prof3ssorSt3v3/media-sample-files/raw/master/hal-9000.mp3"; @@ -165,7 +166,7 @@ await DisplayAlertAsync("Error Loading URL Source", "No value was found to load async void ChangeSourceClicked(object? sender, EventArgs? e) { var result = await DisplayActionSheetAsync("Choose a source", "Cancel", null, - loadOnlineMp4, loadHls, loadLocalResource, resetSource, loadMusic); + loadOnlineMp4, loadHls, loadLocalResource, resetSource, loadMusic, loadFromFile); MediaElement.Stop(); MediaElement.Source = null; @@ -188,14 +189,14 @@ async void ChangeSourceClicked(object? sender, EventArgs? e) return; case resetSource: - MediaElement.MetadataArtworkSource = string.Empty; + MediaElement.MetadataArtworkSource = null; MediaElement.MetadataTitle = string.Empty; MediaElement.MetadataArtist = string.Empty; MediaElement.Source = null; return; case loadLocalResource: - MediaElement.MetadataArtworkSource = botImageUrl; + MediaElement.MetadataArtworkSource = MediaSource.FromResource("robot.jpg"); MediaElement.MetadataTitle = "Local Resource Title"; MediaElement.MetadataArtist = "Local Resource Album"; @@ -220,6 +221,20 @@ async void ChangeSourceClicked(object? sender, EventArgs? e) MediaElement.MetadataArtworkSource = botImageUrl; MediaElement.Source = MediaSource.FromUri(hal9000AudioUrl); return; + case loadFromFile: + var fileResult = await PickAndShow(new PickOptions + { + FileTypes = FilePickerFileType.Videos, + PickerTitle = "Please select a video file" + }); + if (fileResult is not null) + { + MediaElement.MetadataTitle = fileResult.FileName; + MediaElement.MetadataArtist = "From File Album"; + MediaElement.MetadataArtworkSource = botImageUrl; + MediaElement.Source = MediaSource.FromFile(fileResult.FullPath); + } + return; } } @@ -287,4 +302,20 @@ async void DisplayPopup(object? sender, EventArgs? e) popupMediaElement.Stop(); popupMediaElement.Source = null; } + + public async Task PickAndShow(PickOptions options) + { + try + { + var result = await FilePicker.Default.PickAsync(options); + return result; + } + catch (Exception ex) + { + // The user canceled or something went wrong + logger.LogError(ex, "Error picking file"); + } + + return null; + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs deleted file mode 100644 index 59a4a01ba4..0000000000 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.windows.cs +++ /dev/null @@ -1,75 +0,0 @@ -using CommunityToolkit.Maui.Core.Views; -using Microsoft.Maui.Dispatching; -using Windows.Media; - -namespace CommunityToolkit.Maui.Core.Primitives; - -sealed class Metadata -{ - readonly IMediaElement? mediaElement; - readonly SystemMediaTransportControls? systemMediaControls; - readonly IDispatcher dispatcher; - /// - /// Initializes a new instance of the class. - /// - public Metadata(SystemMediaTransportControls systemMediaTransportControls, IMediaElement MediaElement, IDispatcher Dispatcher) - { - mediaElement = MediaElement; - this.dispatcher = Dispatcher; - systemMediaControls = systemMediaTransportControls; - systemMediaControls.ButtonPressed += OnSystemMediaControlsButtonPressed; - } - - - void OnSystemMediaControlsButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args) - { - if (mediaElement is null) - { - return; - } - - if (args.Button == SystemMediaTransportControlsButton.Play) - { - if (dispatcher.IsDispatchRequired) - { - dispatcher.Dispatch(() => mediaElement.Play()); - } - else - { - mediaElement.Play(); - } - } - else if (args.Button == SystemMediaTransportControlsButton.Pause) - { - if (dispatcher.IsDispatchRequired) - { - dispatcher.Dispatch(() => mediaElement.Pause()); - } - else - { - mediaElement.Pause(); - } - } - } - - /// - /// Sets the metadata for the given MediaElement. - /// - public void SetMetadata(IMediaElement mp, MediaManager mediaManager) - { - if (systemMediaControls is null || mediaElement is null || mp is null) - { - return; - } - var source = mediaManager.GetSource(mp.MetadataArtworkSource); - if (!string.IsNullOrEmpty(source)) - { - systemMediaControls.DisplayUpdater.Thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(new Uri(source)); - } - - systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; - systemMediaControls.DisplayUpdater.MusicProperties.Artist = mp.MetadataTitle; - systemMediaControls.DisplayUpdater.MusicProperties.Title = mp.MetadataArtist; - systemMediaControls.DisplayUpdater.Update(); - } -} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 6e4a16a645..da3a1d9a87 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -1,7 +1,5 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Android.Content; -using Android.Graphics; using Android.Views; using Android.Widget; using AndroidX.Media3.Common; @@ -363,15 +361,24 @@ protected virtual async partial ValueTask PlatformUpdateSource() Player.ClearMediaItems(); MediaElement.Duration = TimeSpan.Zero; MediaElement.CurrentStateChanged(MediaElementState.None); - + MediaMetadata.Builder mediaMetaData = new(); + mediaMetaData.SetArtist(string.Empty); + mediaMetaData.SetTitle(string.Empty); + mediaMetaData.SetDescription(string.Empty); + mediaMetaData.SetArtworkUri(null); + mediaMetaData.SetArtworkData(null, null); + mediaItem = new MediaItem.Builder(); + mediaItem.SetMediaId(string.Empty); + mediaItem.SetUri(string.Empty); + mediaItem.SetMediaMetadata(mediaMetaData.Build()); + Player.SetMediaItem(mediaItem.Build()); + UpdateNotifications(); return; } MediaElement.CurrentStateChanged(MediaElementState.Opening); Player.PlayWhenReady = MediaElement.ShouldAutoPlay; - cancellationTokenSource ??= new(); - // ConfigureAwait(true) is required to prevent crash on startup - var result = await SetPlayerData(cancellationTokenSource.Token).ConfigureAwait(true); + var result = await SetPlayerData(MediaElement.Source).ConfigureAwait(true); var item = result?.Build(); if (item?.MediaMetadata is not null) @@ -537,11 +544,127 @@ protected override void Dispose(bool disposing) } } - static async Task GetBytesFromMetadataArtworkUrl(string url, CancellationToken cancellationToken = default) + [MemberNotNull(nameof(connection))] + void StartService() + { + var intent = new Intent(Android.App.Application.Context, typeof(MediaControlsService)); + connection = new BoundServiceConnection(this); + connection.MediaControlsServiceTaskRemoved += HandleMediaControlsServiceTaskRemoved; + + Android.App.Application.Context.StartForegroundService(intent); + Android.App.Application.Context.ApplicationContext?.BindService(intent, connection, Bind.AutoCreate); + } + + void StopService(in BoundServiceConnection boundServiceConnection) + { + boundServiceConnection.MediaControlsServiceTaskRemoved -= HandleMediaControlsServiceTaskRemoved; + + var serviceIntent = new Intent(Platform.AppContext, typeof(MediaControlsService)); + Android.App.Application.Context.StopService(serviceIntent); + Platform.AppContext.UnbindService(boundServiceConnection); + } + + void HandleMediaControlsServiceTaskRemoved(object? sender, EventArgs e) => Player?.Stop(); + + async Task SetPlayerData(MediaSource? mediaSource) + { + var source = GetSource(mediaSource); + return await CreateMediaItem(source).ConfigureAwait(false); + } + + string? GetSource(MediaSource? mediaSource) + { + if (mediaSource is null) + { + return null; + } + + switch (mediaSource) + { + case UriMediaSource uriMediaSource: + { + return uriMediaSource.Uri?.AbsoluteUri; + } + case FileMediaSource fileMediaSource: + { + return fileMediaSource.Path; + } + case ResourceMediaSource resourceMediaSource: + { + var package = PlayerView?.Context?.PackageName ?? ""; + var path = resourceMediaSource.Path; + if (!string.IsNullOrWhiteSpace(path)) + { + return $"asset://{package}{System.IO.Path.PathSeparator}{path}"; + } + + break; + } + default: + throw new NotSupportedException($"{MediaElement.Source?.GetType().FullName} is not yet supported for {nameof(MediaElement.Source)}"); + } + return null; + } + async Task CreateMediaItem(string? url) + { + MediaMetadata.Builder mediaMetaData = new(); + mediaMetaData.SetArtist(MediaElement.MetadataArtist); + mediaMetaData.SetTitle(MediaElement.MetadataTitle); + var data = await GetArtworkFromMediasource(MediaElement.MetadataArtworkSource); + mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover); + + mediaItem = new MediaItem.Builder(); + mediaItem.SetUri(url); + mediaItem.SetMediaId(url); + mediaItem.SetMediaMetadata(mediaMetaData.Build()); + + return mediaItem; + } + + static async Task GetArtworkFromMediasource(MediaSource? mediaSource) + { + if (mediaSource is null) + { + return null; + } + switch (mediaSource) + { + case FileMediaSource fileMediaSource: + { + var filePath = fileMediaSource.Path; + if (filePath is null || string.IsNullOrWhiteSpace(filePath)) + { + return null; + } + return await GetByteArrayFromFile(filePath).ConfigureAwait(false); + } + case ResourceMediaSource resourceMediaSource: + { + var resource = resourceMediaSource.Path; + if (resource is null || string.IsNullOrWhiteSpace(resource)) + { + return null; + } + return await GetMauiAssetBytes(resource).ConfigureAwait(false); + } + case UriMediaSource uriMediaSource: + { + var url = uriMediaSource.Uri?.AbsoluteUri; + if (url is null || string.IsNullOrWhiteSpace(url)) + { + return null; + } + return await GetBytesFromMetadataArtworkUrl(url, CancellationToken.None).ConfigureAwait(false); + } + default: return null; + } + } + + static async Task GetBytesFromMetadataArtworkUrl(string url, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(url)) { - return []; + return null; } Stream? stream = null; @@ -596,8 +719,8 @@ static async Task GetBytesFromMetadataArtworkUrl(string url, Cancellatio } catch (Exception e) { - Trace.WriteLine($"Unable to retrieve {nameof(MediaElement.MetadataArtworkSource)} for {url}.{e}\n"); - return []; + System.Diagnostics.Trace.WriteLine($"Unable to retrieve {nameof(MediaElement.MetadataArtworkSource)} for {url}.{e}\n"); + return null; } finally { @@ -631,90 +754,30 @@ static async ValueTask GetByteCountFromStream(Stream stream, CancellationT } } - [MemberNotNull(nameof(connection))] - void StartService() - { - var intent = new Intent(Android.App.Application.Context, typeof(MediaControlsService)); - connection = new BoundServiceConnection(this); - connection.MediaControlsServiceTaskRemoved += HandleMediaControlsServiceTaskRemoved; - - Android.App.Application.Context.StartForegroundService(intent); - Android.App.Application.Context.ApplicationContext?.BindService(intent, connection, Bind.AutoCreate); - } - - void StopService(in BoundServiceConnection boundServiceConnection) - { - boundServiceConnection.MediaControlsServiceTaskRemoved -= HandleMediaControlsServiceTaskRemoved; - - var serviceIntent = new Intent(Platform.AppContext, typeof(MediaControlsService)); - Android.App.Application.Context.StopService(serviceIntent); - Platform.AppContext.UnbindService(boundServiceConnection); - } - - void HandleMediaControlsServiceTaskRemoved(object? sender, EventArgs e) => Player?.Stop(); - - async Task SetPlayerData(CancellationToken cancellationToken = default) + static async Task GetByteArrayFromFile(string filePath, CancellationToken cancellationToken = default) { - if (MediaElement.Source is null) + if (!File.Exists(filePath)) { return null; } - - switch (MediaElement.Source) - { - case UriMediaSource uriMediaSource: - { - var uri = uriMediaSource.Uri; - if (!string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) - { - return await CreateMediaItem(uri.AbsoluteUri, cancellationToken).ConfigureAwait(false); - } - - break; - } - case FileMediaSource fileMediaSource: - { - var filePath = fileMediaSource.Path; - if (!string.IsNullOrWhiteSpace(filePath)) - { - return await CreateMediaItem(filePath, cancellationToken).ConfigureAwait(false); - } - - break; - } - case ResourceMediaSource resourceMediaSource: - { - var package = PlayerView?.Context?.PackageName ?? ""; - var path = resourceMediaSource.Path; - if (!string.IsNullOrWhiteSpace(path)) - { - var assetFilePath = $"asset://{package}{System.IO.Path.PathSeparator}{path}"; - return await CreateMediaItem(assetFilePath, cancellationToken).ConfigureAwait(false); - } - - break; - } - default: - throw new NotSupportedException($"{MediaElement.Source.GetType().FullName} is not yet supported for {nameof(MediaElement.Source)}"); - } - - return mediaItem; + var stream = File.OpenRead(filePath); + var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, cancellationToken); + var bytes = memoryStream.ToArray(); + return bytes; } - async Task CreateMediaItem(string url, CancellationToken cancellationToken = default) + static async Task GetMauiAssetBytes(string? fileName) { - MediaMetadata.Builder mediaMetaData = new(); - mediaMetaData.SetArtist(MediaElement.MetadataArtist); - mediaMetaData.SetTitle(MediaElement.MetadataTitle); - var data = await GetImageFromMediaSource(MediaElement.MetadataArtworkSource, cancellationToken).ConfigureAwait(true) ?? BlankByteArray(); - mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover); - - mediaItem = new MediaItem.Builder(); - mediaItem.SetUri(url); - mediaItem.SetMediaId(url); - mediaItem.SetMediaMetadata(mediaMetaData.Build()); - - return mediaItem; + if (fileName is null || string.IsNullOrEmpty(fileName)) + { + return null; + } + fileName = System.IO.Path.GetFileName(fileName); + using Stream stream = await FileSystem.OpenAppPackageFileAsync(fileName); + using MemoryStream memoryStream = new(); + stream.CopyTo(memoryStream); + return memoryStream.ToArray(); } #region PlayerListener implementation method stubs @@ -748,90 +811,6 @@ public void OnTimelineChanged(Timeline? timeline, int reason) { } public void OnTrackSelectionParametersChanged(TrackSelectionParameters? trackSelectionParameters) { } public void OnTracksChanged(Tracks? tracks) { } #endregion - static async Task GetImageFromMediaSource(MediaSource? mediaSource, CancellationToken cancellationToken = default) - { - if (mediaSource is null) - { - return null; - } - var artworkUrl = mediaSource; - if (artworkUrl is UriMediaSource uriMediaSource) - { - var uri = uriMediaSource.Uri; - if (string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) - { - System.Diagnostics.Trace.TraceInformation("Arkwork Uri is null or empty"); - return null; - } - return await GetBytesFromMetadataArtworkUrl(uri.AbsoluteUri, cancellationToken).ConfigureAwait(false); - } - else if (artworkUrl is FileMediaSource fileMediaSource) - { - var filePath = fileMediaSource.Path; - if(string.IsNullOrWhiteSpace(filePath)) - { - System.Diagnostics.Trace.TraceInformation("Arkwork File path is null or empty"); - return null; - } - return await GetByteArrayFromFile(filePath, cancellationToken).ConfigureAwait(false); - } - else if (artworkUrl is ResourceMediaSource resourceMediaSource) - { - var path = resourceMediaSource.Path; - var item = path?[(path.LastIndexOf('/') + 1)..]; - if (string.IsNullOrWhiteSpace(item)) - { - System.Diagnostics.Trace.TraceInformation("Arkwork Resource path is null or empty"); - return null; - } - return await GetByteArrayFromResource(item).ConfigureAwait(false); - } - return null; - } - - static async Task GetByteArrayFromResource(string resource) - { - // Android file system does not report size of file so we need to use a buffer for the stream. - using var stream = await FileSystem.Current.OpenAppPackageFileAsync(resource); - if(stream is null) - { - return null; - } - using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); - var bytes = memoryStream.ToArray(); - return bytes; - } - - static async Task GetByteArrayFromFile(string filePath, CancellationToken cancellationToken = default) - { - if(!File.Exists(filePath)) - { - System.Diagnostics.Trace.TraceInformation("Arkwork File does not exist"); - return null; - } - var stream = File.OpenRead(filePath); - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); - var bytes = memoryStream.ToArray(); - return bytes; - } - static byte[] BlankByteArray() - { - var bitmapConfig = Bitmap.Config.Argb8888 ?? throw new InvalidOperationException("Bitmap config cannot be null"); - var bitmap = Bitmap.CreateBitmap(1024, 768, bitmapConfig, true); - - Canvas canvas = new(); - canvas.SetBitmap(bitmap); - canvas.DrawColor(Android.Graphics.Color.Transparent); - canvas.Save(); - - MemoryStream stream = new(); - var format = Bitmap.CompressFormat.Png ?? throw new InvalidOperationException("Bitmap format cannot be null"); - bitmap.Compress(format, 100, stream); - stream.Position = 0; - return stream.ToArray(); - } } static class PlaybackState { diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index fc51d1dd3f..4236bcf2b3 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -2,9 +2,6 @@ using CommunityToolkit.Maui.Extensions; using CommunityToolkit.Maui.Views; using Microsoft.Extensions.Logging; -using Microsoft.Maui; -using Microsoft.Maui.Controls; -using Microsoft.Maui.Dispatching; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Imaging; using Windows.Media; @@ -14,7 +11,6 @@ using Windows.System.Display; using ParentWindow = CommunityToolkit.Maui.Extensions.PageExtensions.ParentWindow; using WindowsMediaElement = Windows.Media.Playback.MediaPlayer; -using WinMediaSource = Windows.Media.Core.MediaSource; namespace CommunityToolkit.Maui.Core.Views; @@ -276,7 +272,7 @@ protected virtual async partial ValueTask PlatformUpdateSource() MediaElement.MediaWidth = MediaElement.MediaHeight = 0; MediaElement.CurrentStateChanged(MediaElementState.None); - + await UpdateMetadata(); return; } @@ -305,15 +301,15 @@ protected virtual async partial ValueTask PlatformUpdateSource() } } /// - /// Gets the string representation of the specified media source, such as a URI, file path, or resource path. + /// Gets the string representation of the specified media source, such as a URI, stream path, or resource path. /// /// The returned value depends on the concrete type of the provided media source. For a URI media - /// source, the absolute URI is returned. For a file media source, the file path is returned. For a resource media - /// source, the full application package file path is returned if available. If the source does not contain a valid + /// source, the absolute URI is returned. For a stream media source, the stream path is returned. For a resource media + /// source, the full application package stream path is returned if available. If the source does not contain a valid /// path or is not recognized, an empty string is returned. - /// The media source to retrieve the string representation for. Can be a URI, file, or resource media source. If null, + /// The media source to retrieve the string representation for. Can be a URI, stream, or resource media source. If null, /// an empty string is returned. - /// A string containing the URI, file path, or resource path of the media source. Returns an empty string if the source + /// A string containing the URI, stream path, or resource path of the media source. Returns an empty string if the source /// is null or does not contain a valid path. public string GetSource(MediaSource? source) { @@ -344,12 +340,7 @@ public string GetSource(MediaSource? source) Logger.LogInformation("ResourceMediaSource Path is null or empty"); return string.Empty; } - - string path = GetFullAppPackageFilePath(resourceMediaSource.Path); - if (!string.IsNullOrWhiteSpace(path)) - { - return path; - } + return resourceMediaSource.Path; } return string.Empty; } @@ -410,60 +401,57 @@ static bool IsZero(TValue numericValue) where TValue : INumber { return TValue.IsZero(numericValue); } - + async ValueTask UpdateMetadata() { - if (systemMediaControls is null || Player is null || MediaElement.MetadataArtworkSource is null) + if (systemMediaControls is null || Player is null) { return; } - - if (MediaElement.MetadataArtworkSource is UriMediaSource uriMediaSource) - { - var artwork = uriMediaSource.Uri?.AbsoluteUri ?? string.Empty; - var file = RandomAccessStreamReference.CreateFromUri(new Uri(artwork)); - if (file is not null) - { - systemMediaControls.DisplayUpdater.Thumbnail = file; - systemMediaControls.DisplayUpdater.Update(); - Uri uri = new(artwork); - Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); - } - } - - if (MediaElement.MetadataArtworkSource is FileMediaSource fileMediaSource) - { - var artwork = fileMediaSource.Path; - if (File.Exists(artwork)) - { - StorageFile ImageFile = await StorageFile.GetFileFromPathAsync(artwork); - Dispatcher.Dispatch(async () => + var source = GetSource(MediaElement.MetadataArtworkSource); + RandomAccessStreamReference? stream = null; + StorageFile? file = null; + Uri? uri = null; + switch (MediaElement.MetadataArtworkSource) + { + case UriMediaSource: + stream = RandomAccessStreamReference.CreateFromUri(new Uri(source)); + uri = new(source); + break; + case FileMediaSource: + if (File.Exists(source)) { - var bitmap = await LoadBitmapImageAsync(ImageFile); - Player.PosterSource = bitmap; - }); - systemMediaControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromFile(ImageFile); - - } + file = await StorageFile.GetFileFromPathAsync(source); + stream = RandomAccessStreamReference.CreateFromFile(file); + uri = new(source); + } + break; + case ResourceMediaSource: + string path = GetFullAppPackageFilePath(source); + file = await StorageFile.GetFileFromPathAsync(path); + stream = RandomAccessStreamReference.CreateFromFile(file); + uri = new(file.Path); + break; + case null: + systemMediaControls.DisplayUpdater.Thumbnail = null; + Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage()); + systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; + systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; + systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; + systemMediaControls.DisplayUpdater.Update(); + break; } - if (MediaElement.MetadataArtworkSource is ResourceMediaSource resourceMediaSource) + if (source is not null) { - var artwork = "ms-appx:///" + resourceMediaSource.Path; - var file = RandomAccessStreamReference.CreateFromUri(new Uri(artwork)); - if (file is not null) - { - systemMediaControls.DisplayUpdater.Thumbnail = file; - systemMediaControls.DisplayUpdater.Update(); - Uri uri = new(artwork); - Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); - } + systemMediaControls.DisplayUpdater.Thumbnail = stream; + Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); + systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; + systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; + systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; + systemMediaControls.DisplayUpdater.Update(); } - - systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; - systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; - systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; - systemMediaControls.DisplayUpdater.Update(); } + static async Task LoadBitmapImageAsync(StorageFile artwork) { var bitmap = new BitmapImage(); From 63ceb1b92eb0cfa9e09b3acb9d13a278b6673c58 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 20:24:10 -0800 Subject: [PATCH 10/44] Refactor GetSource logic; remove LoadBitmapImageAsync Refactored GetSource in MediaManager.macios.cs to use a switch statement with pattern matching for improved readability and maintainability. Removed the unused LoadBitmapImageAsync helper from MediaManager.windows.cs. --- .../Views/MediaManager.macios.cs | 64 +++++++++---------- .../Views/MediaManager.windows.cs | 7 -- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index 39c1bcc9dd..beb08d0ee7 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -428,43 +428,39 @@ protected virtual void Dispose(bool disposing) string? GetSource(MediaSource? source) { - if (source is UriMediaSource uriMediaSource) + switch (source) { - var uri = uriMediaSource.Uri; - if (!string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) - { - return uri.AbsoluteUri; - } - } - else if (source is FileMediaSource fileMediaSource) - { - var uri = fileMediaSource.Path; - - if (!string.IsNullOrWhiteSpace(uri)) - { - return uri; - } - } - else if (source is ResourceMediaSource resourceMediaSource) - { - var path = resourceMediaSource.Path; - - if (!string.IsNullOrWhiteSpace(path) && Path.HasExtension(path)) - { - string directory = Path.GetDirectoryName(path) ?? ""; - string filename = Path.GetFileNameWithoutExtension(path); - string extension = Path.GetExtension(path)[1..]; - var url = NSBundle.MainBundle.GetUrlForResource(filename, - extension, directory); - if (string.IsNullOrEmpty(url.AbsoluteString)) + case UriMediaSource uriMediaSource: + var uri = uriMediaSource.Uri; + if (!string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) { - return url.AbsoluteString; + return uri.AbsoluteUri; } - } - else - { - Logger.LogWarning("Invalid file path for ResourceMediaSource."); - } + break; + case FileMediaSource fileMediaSource: + var uriPath = fileMediaSource.Path; + if (!string.IsNullOrWhiteSpace(uriPath)) + { + return uriPath; + } + break; + case ResourceMediaSource resourceMediaSource: + var path = resourceMediaSource.Path; + if (!string.IsNullOrWhiteSpace(path) && Path.HasExtension(path)) + { + string directory = Path.GetDirectoryName(path) ?? ""; + string filename = Path.GetFileNameWithoutExtension(path); + string extension = Path.GetExtension(path)[1..]; + var url = NSBundle.MainBundle.GetUrlForResource(filename, + extension, directory); + if (!string.IsNullOrEmpty(url.AbsoluteString)) + { + return url.AbsoluteString; + } + } + break; + case null: + return null; } return null; } diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index 4236bcf2b3..bf7ce89434 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -452,13 +452,6 @@ async ValueTask UpdateMetadata() } } - static async Task LoadBitmapImageAsync(StorageFile artwork) - { - var bitmap = new BitmapImage(); - using var randomAccessStream = await artwork.OpenReadAsync(); - await bitmap.SetSourceAsync(randomAccessStream); - return bitmap; - } async void OnMediaElementMediaOpened(WindowsMediaElement sender, object args) { if (Player is null) From df180ff7c93c96ad77b83c8635fbfd879d6daad1 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 20:35:50 -0800 Subject: [PATCH 11/44] Add flag for Android foreground service enablement Introduce isAndroidForegroundServiceEnabled to track service state. Update CreatePlatformView to accept and set this flag. Ensure notifications and service startup only occur when the foreground service is enabled, preventing unnecessary operations. --- .../Views/MediaManager.android.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index b9d4e38789..1990100ef9 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -29,6 +29,7 @@ public partial class MediaManager : Java.Lang.Object, IPlayerListener double? previousSpeed; float volumeBeforeMute = 1; + bool isAndroidForegroundServiceEnabled = false; TaskCompletionSource? seekToTaskCompletionSource; CancellationTokenSource? cancellationTokenSource; MediaSession? session; @@ -60,7 +61,7 @@ public void OnPlaybackParametersChanged(PlaybackParameters? playbackParameters) public void UpdateNotifications() { - if (connection?.Binder?.Service is null) + if (connection?.Binder?.Service is null || !isAndroidForegroundServiceEnabled) { System.Diagnostics.Trace.TraceInformation("Notification Service not running."); return; @@ -128,10 +129,11 @@ or PlaybackState.StateSkippingToQueueItem /// The platform native counterpart of . /// Thrown when is or when the platform view could not be created. [MemberNotNull(nameof(Player), nameof(PlayerView), nameof(session))] - public (PlatformMediaElement platformView, PlayerView PlayerView) CreatePlatformView(AndroidViewType androidViewType) + public (PlatformMediaElement platformView, PlayerView PlayerView) CreatePlatformView(AndroidViewType androidViewType, bool isAndroidServiceEnabled) { Player = new ExoPlayerBuilder(MauiContext.Context).Build() ?? throw new InvalidOperationException("Player cannot be null"); Player.AddListener(this); + this.isAndroidForegroundServiceEnabled = isAndroidServiceEnabled; if (androidViewType is AndroidViewType.SurfaceView) { @@ -351,7 +353,7 @@ protected virtual async partial ValueTask PlatformUpdateSource() return; } - if (connection is null) + if (connection is null && isAndroidForegroundServiceEnabled) { StartService(); } From a38dfd892340d456298ba2cef91df5d8a0a8e5e3 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 20:38:08 -0800 Subject: [PATCH 12/44] Remove trace log when notification service not running Removed unnecessary trace message in UpdateNotifications; now the method returns silently if the notification service is not running. This reduces log noise and streamlines error handling. --- .../Views/MediaManager.android.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 1990100ef9..96172ff83f 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -63,7 +63,6 @@ public void UpdateNotifications() { if (connection?.Binder?.Service is null || !isAndroidForegroundServiceEnabled) { - System.Diagnostics.Trace.TraceInformation("Notification Service not running."); return; } From b7c83bdf212e9e85eca4c0715e4db3c78b4920d6 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 20:43:06 -0800 Subject: [PATCH 13/44] Separate MediaOpened and notification update conditions Refactored PlatformUpdateSource to call MediaElement.MediaOpened() only when there is no PlayerError, and to update notifications only when the Android foreground service is enabled. This improves clarity and ensures notifications are updated independently of media errors. --- .../Views/MediaManager.android.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 96172ff83f..a32cb1bb7c 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -389,10 +389,16 @@ protected virtual async partial ValueTask PlatformUpdateSource() hasSetSource = true; } - if (hasSetSource && Player.PlayerError is null) + if (hasSetSource) { - MediaElement.MediaOpened(); - UpdateNotifications(); + if (Player.PlayerError is null) + { + MediaElement.MediaOpened(); + } + if (isAndroidForegroundServiceEnabled) + { + UpdateNotifications(); + } } } From 5653940d41b3ecca050fa5bb2b6488e26eef88c0 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 21:04:18 -0800 Subject: [PATCH 14/44] Improve async stream handling and resource disposal Refactored file and memory stream usage to use 'using' statements for proper disposal. Replaced synchronous CopyTo with asynchronous CopyToAsync for non-blocking I/O. Removed redundant byte array conversions by returning the result directly. --- .../Views/MediaManager.android.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index a32cb1bb7c..27dfb401cb 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -767,11 +767,10 @@ static async ValueTask GetByteCountFromStream(Stream stream, CancellationT { return null; } - var stream = File.OpenRead(filePath); - var memoryStream = new MemoryStream(); + using var stream = File.OpenRead(filePath); + using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream, cancellationToken); - var bytes = memoryStream.ToArray(); - return bytes; + return memoryStream.ToArray(); } static async Task GetMauiAssetBytes(string? fileName) @@ -783,7 +782,7 @@ static async ValueTask GetByteCountFromStream(Stream stream, CancellationT fileName = System.IO.Path.GetFileName(fileName); using Stream stream = await FileSystem.OpenAppPackageFileAsync(fileName); using MemoryStream memoryStream = new(); - stream.CopyTo(memoryStream); + await stream.CopyToAsync(memoryStream); return memoryStream.ToArray(); } From 3a655dd80c9be27ac0bd979acf6dccdb1ee09134 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 21:19:52 -0800 Subject: [PATCH 15/44] Refactor media source handling and metadata updates - Change GetSource to internal and remove XML docs. - Wrap ResourceMediaSource file access in try-catch; log warning if not found. - Only set thumbnail/poster if stream and uri are valid. - Always update system media controls metadata. - Clean up and reorganize redundant updater code. --- .../Views/MediaManager.windows.cs | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index bf7ce89434..cc1a69f6ad 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -300,18 +300,8 @@ protected virtual async partial ValueTask PlatformUpdateSource() } } } - /// - /// Gets the string representation of the specified media source, such as a URI, stream path, or resource path. - /// - /// The returned value depends on the concrete type of the provided media source. For a URI media - /// source, the absolute URI is returned. For a stream media source, the stream path is returned. For a resource media - /// source, the full application package stream path is returned if available. If the source does not contain a valid - /// path or is not recognized, an empty string is returned. - /// The media source to retrieve the string representation for. Can be a URI, stream, or resource media source. If null, - /// an empty string is returned. - /// A string containing the URI, stream path, or resource path of the media source. Returns an empty string if the source - /// is null or does not contain a valid path. - public string GetSource(MediaSource? source) + + string GetSource(MediaSource? source) { if (source == null) { @@ -427,29 +417,35 @@ async ValueTask UpdateMetadata() } break; case ResourceMediaSource: - string path = GetFullAppPackageFilePath(source); - file = await StorageFile.GetFileFromPathAsync(path); - stream = RandomAccessStreamReference.CreateFromFile(file); - uri = new(file.Path); + try + { + string path = GetFullAppPackageFilePath(source); + file = await StorageFile.GetFileFromPathAsync(path); + stream = RandomAccessStreamReference.CreateFromFile(file); + uri = new(file.Path); + } + catch (FileNotFoundException e) + { + Logger?.LogWarning("ResourceMediaSource file not found: {message}", e.Message); + } break; case null: systemMediaControls.DisplayUpdater.Thumbnail = null; Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage()); - systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; - systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; - systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; - systemMediaControls.DisplayUpdater.Update(); break; } - if (source is not null) + + if (source is not null && stream is not null && uri is not null) { systemMediaControls.DisplayUpdater.Thumbnail = stream; Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; - systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; - systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; - systemMediaControls.DisplayUpdater.Update(); } + + systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; + systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; + systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; + systemMediaControls.DisplayUpdater.Update(); } async void OnMediaElementMediaOpened(WindowsMediaElement sender, object args) From f13d6d6fa7cdd7514254a0f346a35ca9191ec7c9 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 21:28:23 -0800 Subject: [PATCH 16/44] Update src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/MediaManager.windows.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index cc1a69f6ad..1a22374c97 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -443,8 +443,8 @@ async ValueTask UpdateMetadata() } systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; - systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataTitle; - systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataArtist; + systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataArtist; + systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataTitle; systemMediaControls.DisplayUpdater.Update(); } From 48f192cde729d861bf79d1017b72f0983458459e Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 21:29:43 -0800 Subject: [PATCH 17/44] Update src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/MediaManager.windows.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index 1a22374c97..a472fc0ba7 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -405,8 +405,15 @@ async ValueTask UpdateMetadata() switch (MediaElement.MetadataArtworkSource) { case UriMediaSource: - stream = RandomAccessStreamReference.CreateFromUri(new Uri(source)); - uri = new(source); + if (!string.IsNullOrWhiteSpace(source) && Uri.TryCreate(source, UriKind.Absolute, out var artworkUri)) + { + stream = RandomAccessStreamReference.CreateFromUri(artworkUri); + uri = artworkUri; + } + else + { + Logger?.LogWarning("UriMediaSource metadata artwork source is null, empty, or invalid."); + } break; case FileMediaSource: if (File.Exists(source)) From 0eea2d7230e5c1d98ef5c73fad6f8ff1b913e394 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 21:47:24 -0800 Subject: [PATCH 18/44] Improve media service checks and asset loading on Android/Win - Add foreground service enabled check to Android StopService - Make GetMauiAssetBytes cancellable via CancellationToken - Log and handle missing/empty sources in Windows media update - Always set DisplayUpdater.Type to Music in Windows metadata --- .../Primitives/Metadata.macios.cs | 2 +- .../Views/MediaManager.android.cs | 8 ++++++-- .../Views/MediaManager.windows.cs | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index f0e4fc14ad..f04a34fd87 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -225,7 +225,7 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) using var memoryStream = new MemoryStream(); if (inputStream is null) { - System.Diagnostics.Trace.TraceInformation($"{inputStream} is null."); + System.Diagnostics.Trace.WriteLine($"{inputStream} is null."); return null; } await inputStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 27dfb401cb..df3f40408d 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -564,6 +564,10 @@ void StartService() void StopService(in BoundServiceConnection boundServiceConnection) { + if (!isAndroidForegroundServiceEnabled) + { + return; + } boundServiceConnection.MediaControlsServiceTaskRemoved -= HandleMediaControlsServiceTaskRemoved; var serviceIntent = new Intent(Platform.AppContext, typeof(MediaControlsService)); @@ -773,7 +777,7 @@ static async ValueTask GetByteCountFromStream(Stream stream, CancellationT return memoryStream.ToArray(); } - static async Task GetMauiAssetBytes(string? fileName) + static async Task GetMauiAssetBytes(string? fileName, CancellationToken cancellationToken = default) { if (fileName is null || string.IsNullOrEmpty(fileName)) { @@ -782,7 +786,7 @@ static async ValueTask GetByteCountFromStream(Stream stream, CancellationT fileName = System.IO.Path.GetFileName(fileName); using Stream stream = await FileSystem.OpenAppPackageFileAsync(fileName); using MemoryStream memoryStream = new(); - await stream.CopyToAsync(memoryStream); + await stream.CopyToAsync(memoryStream, cancellationToken); return memoryStream.ToArray(); } diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index a472fc0ba7..458f2e04d8 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -281,13 +281,22 @@ protected virtual async partial ValueTask PlatformUpdateSource() Player.AutoPlay = MediaElement.ShouldAutoPlay; var source = GetSource(MediaElement.Source); - + if (string.IsNullOrWhiteSpace(source)) + { + Logger.LogWarning("MediaElement Source is null or empty."); + return; + } if (MediaElement.Source is UriMediaSource) { Player.MediaPlayer.SetUriSource(new Uri(source)); } else if (MediaElement.Source is FileMediaSource) { + if (!File.Exists(source)) + { + Logger.LogWarning("FileMediaSource file not found: {FilePath}", source); + return; + } StorageFile storageFile = await StorageFile.GetFileFromPathAsync(source); Player.MediaPlayer.SetFileSource(storageFile); } @@ -398,7 +407,9 @@ async ValueTask UpdateMetadata() { return; } + var source = GetSource(MediaElement.MetadataArtworkSource); + RandomAccessStreamReference? stream = null; StorageFile? file = null; Uri? uri = null; @@ -446,7 +457,6 @@ async ValueTask UpdateMetadata() { systemMediaControls.DisplayUpdater.Thumbnail = stream; Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); - systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; } systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; From 1baf9b1f1d6c73abb98f4092a571779df3341cb9 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 21:57:23 -0800 Subject: [PATCH 19/44] Add CancellationToken support to GetArtworkFromMediasource GetArtworkFromMediasource and its helper methods now accept an optional CancellationToken parameter, enabling cancellation of asynchronous operations for improved responsiveness and resource management. --- .../Views/MediaManager.android.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index df3f40408d..947fe21b56 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -632,7 +632,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) return mediaItem; } - static async Task GetArtworkFromMediasource(MediaSource? mediaSource) + static async Task GetArtworkFromMediasource(MediaSource? mediaSource, CancellationToken cancellationToken = default) { if (mediaSource is null) { @@ -647,7 +647,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) { return null; } - return await GetByteArrayFromFile(filePath).ConfigureAwait(false); + return await GetByteArrayFromFile(filePath, cancellationToken).ConfigureAwait(false); } case ResourceMediaSource resourceMediaSource: { @@ -656,7 +656,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) { return null; } - return await GetMauiAssetBytes(resource).ConfigureAwait(false); + return await GetMauiAssetBytes(resource, cancellationToken).ConfigureAwait(false); } case UriMediaSource uriMediaSource: { @@ -665,7 +665,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) { return null; } - return await GetBytesFromMetadataArtworkUrl(url, CancellationToken.None).ConfigureAwait(false); + return await GetBytesFromMetadataArtworkUrl(url, cancellationToken).ConfigureAwait(false); } default: return null; } From 41ad3e346213368fb7a37f8d172560bcc0fd4b35 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 21:58:13 -0800 Subject: [PATCH 20/44] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Pages/Views/MediaElement/MediaElementPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index 9298bf90a2..a6e775cd47 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -234,7 +234,7 @@ async void ChangeSourceClicked(object? sender, EventArgs? e) MediaElement.MetadataArtworkSource = botImageUrl; MediaElement.Source = MediaSource.FromFile(fileResult.FullPath); } - return; + return; } } From 99707218ee0cc3fecdd82e997e619b895e59971d Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 22:01:06 -0800 Subject: [PATCH 21/44] Rename MetadataArtworkUrl to MetadataArtworkMediaSource Renamed the MetadataArtworkUrl method and all its references to MetadataArtworkMediaSource to better reflect that it operates on a MediaSource, not just a URL. This improves code clarity and intent. --- .../Primitives/Metadata.macios.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index f04a34fd87..82d8c72a26 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -77,7 +77,7 @@ public async Task SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElem return; } ClearNowPlaying(); - var artwork = await MetadataArtworkUrl(mediaElement.MetadataArtworkSource).ConfigureAwait(false); + var artwork = await MetadataArtworkMediaSource(mediaElement.MetadataArtworkSource).ConfigureAwait(false); if (artwork is UIImage image) { @@ -173,7 +173,7 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) return MPRemoteCommandHandlerStatus.Success; } - public static async Task MetadataArtworkUrl(MediaSource? artworkUrl, CancellationToken cancellationToken = default) + public static async Task MetadataArtworkMediaSource(MediaSource? artworkUrl, CancellationToken cancellationToken = default) { if (artworkUrl is UriMediaSource uriMediaSource) { From aebcfc3ea6f39640105089c6ff401fe1dc15c0bf Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Thu, 29 Jan 2026 22:23:38 -0800 Subject: [PATCH 22/44] Refactor artwork source handling and add cancellation support Refactored MetadataArtworkMediaSource to use a switch statement for improved readability and maintainability. Updated CreateMediaItem in MediaManager.android.cs to accept a CancellationToken, enabling cancellation during asynchronous artwork retrieval. --- .../Primitives/Metadata.macios.cs | 28 +++++++++---------- .../Views/MediaManager.android.cs | 4 +-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index 82d8c72a26..8eb5c4ff50 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -175,21 +175,19 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) public static async Task MetadataArtworkMediaSource(MediaSource? artworkUrl, CancellationToken cancellationToken = default) { - if (artworkUrl is UriMediaSource uriMediaSource) - { - var uri = uriMediaSource.Uri; - return GetBitmapFromUrl(uri?.AbsoluteUri); - } - else if (artworkUrl is FileMediaSource fileMediaSource) - { - var uri = fileMediaSource.Path; - - return await GetBitmapFromFile(uri, cancellationToken).ConfigureAwait(false); - } - else if (artworkUrl is ResourceMediaSource resourceMediaSource) - { - var path = resourceMediaSource.Path; - return await GetBitmapFromResource(path, cancellationToken).ConfigureAwait(false); + switch(artworkUrl) + { + case UriMediaSource uriMediaSource: + var uri = uriMediaSource.Uri; + return GetBitmapFromUrl(uri?.AbsoluteUri); + case FileMediaSource fileMediaSource: + var uriFile = fileMediaSource.Path; + return await GetBitmapFromFile(uriFile, cancellationToken).ConfigureAwait(false); + case ResourceMediaSource resourceMediaSource: + var path = resourceMediaSource.Path; + return await GetBitmapFromResource(path, cancellationToken).ConfigureAwait(false); + case null: + return null; } return null; } diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 947fe21b56..57d27eb9aa 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -616,12 +616,12 @@ void StopService(in BoundServiceConnection boundServiceConnection) } return null; } - async Task CreateMediaItem(string? url) + async Task CreateMediaItem(string? url, CancellationToken cancellationToken = default) { MediaMetadata.Builder mediaMetaData = new(); mediaMetaData.SetArtist(MediaElement.MetadataArtist); mediaMetaData.SetTitle(MediaElement.MetadataTitle); - var data = await GetArtworkFromMediasource(MediaElement.MetadataArtworkSource); + var data = await GetArtworkFromMediasource(MediaElement.MetadataArtworkSource, cancellationToken); mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover); mediaItem = new MediaItem.Builder(); From 2067ead4c75140ea131c3c87c732de849b58ec2b Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 11:17:12 -0800 Subject: [PATCH 23/44] Refactor MediaElement artwork handling and cleanup Replaces MetadataArtworkUrl with MetadataArtworkSource using MediaSource for greater flexibility. Updates property binding and accessors. Simplifies Android media item clearing by setting to null instead of empty metadata. Removes SetPlayerData and inlines its logic for improved clarity and maintainability. --- .../MediaElement.shared.cs | 12 ++++++---- .../Views/MediaManager.android.cs | 23 ++++--------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs index 1668142d8f..15c008ce76 100644 --- a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs +++ b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs @@ -117,9 +117,9 @@ public partial class MediaElement : View, IMediaElement, IDisposable public static readonly BindableProperty MetadataArtistProperty = BindableProperty.Create(nameof(MetadataArtist), typeof(string), typeof(MediaElement), MediaElementDefaults.MetadataArtist); /// - /// Bindable property for the property. + /// Bindable property for the property. /// - public static readonly BindableProperty MetadataArtworkUrlProperty = BindableProperty.Create(nameof(MetadataArtworkUrl), typeof(string), typeof(MediaElement), MediaElementDefaults.MetadataArtworkUrl); + public static readonly BindableProperty MetadataArtworkSourceProperty = BindableProperty.Create(nameof(MetadataArtworkSource), typeof(string), typeof(MediaElement), MediaElementDefaults.MetadataArtworkSource); readonly WeakEventManager eventManager = new(); readonly SemaphoreSlim seekToSemaphoreSlim = new(1, 1); @@ -335,10 +335,12 @@ public string MetadataArtist /// /// Gets or sets the of the media. /// - [BindableProperty] [TypeConverter(typeof(MediaSourceConverter))] - public partial MediaSource? MetadataArtworkSource { get; set; } = MediaElementDefaults.MetadataArtworkSource; - + public MediaSource? MetadataArtworkSource + { + get => (MediaSource?)GetValue(MetadataArtworkSourceProperty); + set => SetValue(MetadataArtworkSourceProperty, value); + } /// /// Gets or sets the of the media. /// diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 57d27eb9aa..ed6d166534 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -362,25 +362,16 @@ protected virtual async partial ValueTask PlatformUpdateSource() Player.ClearMediaItems(); MediaElement.Duration = TimeSpan.Zero; MediaElement.CurrentStateChanged(MediaElementState.None); - MediaMetadata.Builder mediaMetaData = new(); - mediaMetaData.SetArtist(string.Empty); - mediaMetaData.SetTitle(string.Empty); - mediaMetaData.SetDescription(string.Empty); - mediaMetaData.SetArtworkUri(null); - mediaMetaData.SetArtworkData(null, null); - mediaItem = new MediaItem.Builder(); - mediaItem.SetMediaId(string.Empty); - mediaItem.SetUri(string.Empty); - mediaItem.SetMediaMetadata(mediaMetaData.Build()); - Player.SetMediaItem(mediaItem.Build()); + Player.SetMediaItem(null); UpdateNotifications(); return; } MediaElement.CurrentStateChanged(MediaElementState.Opening); Player.PlayWhenReady = MediaElement.ShouldAutoPlay; - var result = await SetPlayerData(MediaElement.Source).ConfigureAwait(true); - var item = result?.Build(); + var source = GetSource(MediaElement.Source); + var result = await CreateMediaItem(source).ConfigureAwait(false); + var item = result.Build(); if (item?.MediaMetadata is not null) { @@ -577,12 +568,6 @@ void StopService(in BoundServiceConnection boundServiceConnection) void HandleMediaControlsServiceTaskRemoved(object? sender, EventArgs e) => Player?.Stop(); - async Task SetPlayerData(MediaSource? mediaSource) - { - var source = GetSource(mediaSource); - return await CreateMediaItem(source).ConfigureAwait(false); - } - string? GetSource(MediaSource? mediaSource) { if (mediaSource is null) From 8faa0bfb55f28db1960c56028efb1886a3588501 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 11:54:27 -0800 Subject: [PATCH 24/44] Update MediaElement artwork handling to use MediaSource MetadataArtworkSource now accepts MediaSource for flexible artwork assignment (file or URI). File picker flow updated to allow image selection for artwork. Minor cleanup in Android source update logic. --- .../Views/MediaElement/MediaElementPage.xaml.cs | 15 ++++++++++++--- .../MediaElement.shared.cs | 2 +- .../Views/MediaManager.android.cs | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index a6e775cd47..7e2fb83e68 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -223,15 +223,24 @@ async void ChangeSourceClicked(object? sender, EventArgs? e) return; case loadFromFile: var fileResult = await PickAndShow(new PickOptions + { + FileTypes = FilePickerFileType.Images, + PickerTitle = "Please select a Image file" + }); + if (fileResult is not null) + { + MediaElement.MetadataArtworkSource = MediaSource.FromFile(fileResult.FullPath); + } + MediaElement.MetadataTitle = "Downloaded file"; + MediaElement.MetadataArtist = "From File Album"; + + fileResult = await PickAndShow(new PickOptions { FileTypes = FilePickerFileType.Videos, PickerTitle = "Please select a video file" }); if (fileResult is not null) { - MediaElement.MetadataTitle = fileResult.FileName; - MediaElement.MetadataArtist = "From File Album"; - MediaElement.MetadataArtworkSource = botImageUrl; MediaElement.Source = MediaSource.FromFile(fileResult.FullPath); } return; diff --git a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs index 15c008ce76..89a1c8fb16 100644 --- a/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs +++ b/src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs @@ -119,7 +119,7 @@ public partial class MediaElement : View, IMediaElement, IDisposable /// /// Bindable property for the property. /// - public static readonly BindableProperty MetadataArtworkSourceProperty = BindableProperty.Create(nameof(MetadataArtworkSource), typeof(string), typeof(MediaElement), MediaElementDefaults.MetadataArtworkSource); + public static readonly BindableProperty MetadataArtworkSourceProperty = BindableProperty.Create(nameof(MetadataArtworkSource), typeof(MediaSource), typeof(MediaElement), MediaElementDefaults.MetadataArtworkSource); readonly WeakEventManager eventManager = new(); readonly SemaphoreSlim seekToSemaphoreSlim = new(1, 1); diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index ed6d166534..43badf663f 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -370,7 +370,7 @@ protected virtual async partial ValueTask PlatformUpdateSource() MediaElement.CurrentStateChanged(MediaElementState.Opening); Player.PlayWhenReady = MediaElement.ShouldAutoPlay; var source = GetSource(MediaElement.Source); - var result = await CreateMediaItem(source).ConfigureAwait(false); + var result = await CreateMediaItem(source); var item = result.Build(); if (item?.MediaMetadata is not null) From 1dcc96d750bce7769fb5c4c1fbc1a3d0268a7183 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 12:51:43 -0800 Subject: [PATCH 25/44] Refactor media source handling for file/URL distinction Refactored GetSource to return both the source string and a flag indicating if it's a file, enabling correct handling of file paths vs. URLs for AVAsset and image loading. Updated PlatformUpdateSource and SetPoster to use this distinction. Made PickAndShow private and fixed its signature. Corrected a typo in the file picker title. Fixed ToggleCommand null check logic. --- .../MediaElement/MediaElementPage.xaml.cs | 4 +- .../Primitives/Metadata.macios.cs | 2 +- .../Views/MediaManager.macios.cs | 39 ++++++++++++++----- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index 7e2fb83e68..b37db46a05 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -225,7 +225,7 @@ async void ChangeSourceClicked(object? sender, EventArgs? e) var fileResult = await PickAndShow(new PickOptions { FileTypes = FilePickerFileType.Images, - PickerTitle = "Please select a Image file" + PickerTitle = "Please select an image file" }); if (fileResult is not null) { @@ -312,7 +312,7 @@ async void DisplayPopup(object? sender, EventArgs? e) popupMediaElement.Source = null; } - public async Task PickAndShow(PickOptions options) + async Task PickAndShow(PickOptions options) { try { diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index 8eb5c4ff50..b6826b8cd1 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -156,7 +156,7 @@ MPRemoteCommandHandlerStatus PauseCommand(MPRemoteCommandEvent? commandEvent) MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) { - if (commandEvent is not null) + if (commandEvent is null) { return MPRemoteCommandHandlerStatus.CommandFailed; } diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index beb08d0ee7..482ad415cd 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -223,10 +223,19 @@ protected virtual async partial ValueTask PlatformUpdateSource() metaData ??= new(Player); Metadata.ClearNowPlaying(); PlayerViewController?.ContentOverlayView?.Subviews.FirstOrDefault()?.RemoveFromSuperview(); - var source = GetSource(MediaElement.Source); + string? source = string.Empty; + bool isFileSource = false; + (source, isFileSource) = GetSource(MediaElement.Source); if (!string.IsNullOrWhiteSpace(source)) { - asset = AVAsset.FromUrl(new NSUrl(source)); + if (isFileSource) + { + asset = AVAsset.FromUrl(NSUrl.FromFilename(source)); + } + else + { + asset = AVAsset.FromUrl(new NSUrl(source)); + } } PlayerItem = asset is not null ? new AVPlayerItem(asset) @@ -426,7 +435,7 @@ protected virtual void Dispose(bool disposing) static TimeSpan ConvertTime(CMTime cmTime) => TimeSpan.FromSeconds(double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds); - string? GetSource(MediaSource? source) + (string?, bool isFileSource) GetSource(MediaSource? source) { switch (source) { @@ -434,14 +443,14 @@ protected virtual void Dispose(bool disposing) var uri = uriMediaSource.Uri; if (!string.IsNullOrWhiteSpace(uri?.AbsoluteUri)) { - return uri.AbsoluteUri; + return (uri.AbsoluteUri, false); } break; case FileMediaSource fileMediaSource: var uriPath = fileMediaSource.Path; if (!string.IsNullOrWhiteSpace(uriPath)) { - return uriPath; + return (uriPath, true); } break; case ResourceMediaSource resourceMediaSource: @@ -455,14 +464,14 @@ protected virtual void Dispose(bool disposing) extension, directory); if (!string.IsNullOrEmpty(url.AbsoluteString)) { - return url.AbsoluteString; + return (url.AbsoluteString, false); } } break; case null: - return null; + return (null, false); } - return null; + return (null, false); } static async Task<(int Width, int Height)> GetVideoDimensions(AVPlayerItem avPlayerItem) { @@ -562,12 +571,22 @@ async Task SetPoster() if (PlayerViewController?.View is not null && PlayerViewController.ContentOverlayView is not null) { - var source = GetSource(MediaElement.MetadataArtworkSource); + string? source = null; + bool isFileSource = false; + (source, isFileSource) = GetSource(MediaElement.MetadataArtworkSource); if (string.IsNullOrWhiteSpace(source)) { return; } - var image = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(source))) ?? new UIImage(); + UIImage? image = null; + if (isFileSource) + { + image = UIImage.FromFile(source) ?? new UIImage(); + } + else + { + image = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(source))) ?? new UIImage(); + } var imageView = new UIImageView(image) { ContentMode = UIViewContentMode.ScaleAspectFit, From 96bc04c83dd5d1c6958d29bb4d16658d564681f9 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 13:51:32 -0800 Subject: [PATCH 26/44] Improve metadata handling and file access robustness Refactored MediaElementDefaults and made MetadataArtworkSource an expression-bodied property. Updated CreateMediaItem to require a CancellationToken and fixed file access mode to prevent overwrites. Made GetSource static in macOS code. Added checks and improved logging for empty resource paths and file-not-found exceptions in Windows metadata updates. --- .../Primitives/MediaElementDefaults.shared.cs | 2 +- .../Views/MediaManager.android.cs | 7 ++++--- .../Views/MediaManager.macios.cs | 2 +- .../Views/MediaManager.windows.cs | 9 +++++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs index fc1384b173..90dab21623 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/MediaElementDefaults.shared.cs @@ -28,7 +28,7 @@ static class MediaElementDefaults public const string MetadataArtist = ""; - public static MediaSource? MetadataArtworkSource { get; } = null; + public static MediaSource? MetadataArtworkSource => null; public const MediaElementState CurrentState = MediaElementState.None; diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 43badf663f..2256828a77 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -370,7 +370,8 @@ protected virtual async partial ValueTask PlatformUpdateSource() MediaElement.CurrentStateChanged(MediaElementState.Opening); Player.PlayWhenReady = MediaElement.ShouldAutoPlay; var source = GetSource(MediaElement.Source); - var result = await CreateMediaItem(source); + cancellationTokenSource = new CancellationTokenSource(); + var result = await CreateMediaItem(source, cancellationTokenSource.Token); var item = result.Build(); if (item?.MediaMetadata is not null) @@ -601,7 +602,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) } return null; } - async Task CreateMediaItem(string? url, CancellationToken cancellationToken = default) + async Task CreateMediaItem(string? url, CancellationToken cancellationToken) { MediaMetadata.Builder mediaMetaData = new(); mediaMetaData.SetArtist(MediaElement.MetadataArtist); @@ -687,7 +688,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) { var normalizedFilePath = NormalizeFilePath(url); - stream = File.Open(normalizedFilePath, FileMode.Create); + stream = File.Open(normalizedFilePath, FileMode.Open); contentLength = await GetByteCountFromStream(stream, cancellationToken); } // Relative File Path diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index 482ad415cd..33af1cd565 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -435,7 +435,7 @@ protected virtual void Dispose(bool disposing) static TimeSpan ConvertTime(CMTime cmTime) => TimeSpan.FromSeconds(double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds); - (string?, bool isFileSource) GetSource(MediaSource? source) + static (string?, bool isFileSource) GetSource(MediaSource? source) { switch (source) { diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index 458f2e04d8..f4b6fb81ef 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -400,7 +400,7 @@ static bool IsZero(TValue numericValue) where TValue : INumber { return TValue.IsZero(numericValue); } - + async ValueTask UpdateMetadata() { if (systemMediaControls is null || Player is null) @@ -437,6 +437,11 @@ async ValueTask UpdateMetadata() case ResourceMediaSource: try { + if (string.IsNullOrEmpty(source)) + { + Logger?.LogWarning("ResourceMediaSource metadata artwork source path is null or empty."); + return; + } string path = GetFullAppPackageFilePath(source); file = await StorageFile.GetFileFromPathAsync(path); stream = RandomAccessStreamReference.CreateFromFile(file); @@ -444,7 +449,7 @@ async ValueTask UpdateMetadata() } catch (FileNotFoundException e) { - Logger?.LogWarning("ResourceMediaSource file not found: {message}", e.Message); + Logger?.LogWarning("ResourceMediaSource file not found: {Message}", e.Message); } break; case null: From cdcbd0c4ce1f934c92a961dfc52e866aaaed3f7d Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:03:04 -0800 Subject: [PATCH 27/44] Add CancellationToken to SetMetadata and minor cleanups SetMetadata now supports an optional CancellationToken for async artwork loading cancellation. Cleaned up source variable initialization in PlatformUpdateSource and made a minor formatting adjustment in SetPoster. No functional changes to poster logic. --- .../Primitives/Metadata.macios.cs | 5 +++-- .../Views/MediaManager.macios.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index b6826b8cd1..4807d9fb81 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -70,14 +70,15 @@ public Metadata(PlatformMediaElement player) /// /// /// - public async Task SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElement) + /// + public async Task SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElement, CancellationToken cancellationToken = default) { if (mediaElement is null) { return; } ClearNowPlaying(); - var artwork = await MetadataArtworkMediaSource(mediaElement.MetadataArtworkSource).ConfigureAwait(false); + var artwork = await MetadataArtworkMediaSource(mediaElement.MetadataArtworkSource, cancellationToken).ConfigureAwait(false); if (artwork is UIImage image) { diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index 33af1cd565..a06e929358 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -223,7 +223,7 @@ protected virtual async partial ValueTask PlatformUpdateSource() metaData ??= new(Player); Metadata.ClearNowPlaying(); PlayerViewController?.ContentOverlayView?.Subviews.FirstOrDefault()?.RemoveFromSuperview(); - string? source = string.Empty; + string? source; bool isFileSource = false; (source, isFileSource) = GetSource(MediaElement.Source); if (!string.IsNullOrWhiteSpace(source)) @@ -602,7 +602,7 @@ async Task SetPoster() imageView.CenterYAnchor.ConstraintEqualTo(PlayerViewController.ContentOverlayView.CenterYAnchor), imageView.WidthAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.WidthAnchor), imageView.HeightAnchor.ConstraintLessThanOrEqualTo(PlayerViewController.ContentOverlayView.HeightAnchor), - + // Maintain the aspect ratio imageView.WidthAnchor.ConstraintEqualTo(imageView.HeightAnchor, image.Size.Width / image.Size.Height) ]); From d2e0dfe8ec49f1ed40c13c7c71d83f88249d2bfd Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:24:49 -0800 Subject: [PATCH 28/44] Improve file error handling and async metadata updates - Enhance GetBitmapFromFile to check file existence and handle IO/permission errors with logging. - Make TimeControlStatusChanged async, add null check for metaData, and await SetMetadata with CancellationToken. --- .../Primitives/Metadata.macios.cs | 29 ++++++++++++++----- .../Views/MediaManager.macios.cs | 8 ++--- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index 4807d9fb81..ce6cd294a8 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -195,17 +195,32 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) static async Task GetBitmapFromFile(string? resource, CancellationToken cancellationToken = default) { - if (string.IsNullOrWhiteSpace(resource)) + if (!File.Exists(resource)) { + System.Diagnostics.Trace.WriteLine($"Metadata artwork file not found: '{resource}'."); + return null; + } + try + { + using var fileStream = File.OpenRead(resource); + using var memoryStream = new MemoryStream(); + await fileStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; + NSData temp = NSData.FromStream(memoryStream) ?? new NSData(); + return UIImage.LoadFromData(temp); + } + catch (IOException ex) + { + System.Diagnostics.Trace.WriteLine($"Error reading metadata artwork file '{resource}': {ex}"); + return null; + } + catch (UnauthorizedAccessException ex) + { + System.Diagnostics.Trace.WriteLine($"Access denied reading metadata artwork file '{resource}': {ex}"); return null; } - using var fileStream = File.OpenRead(resource); - using var memoryStream = new MemoryStream(); - await fileStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); - memoryStream.Position = 0; - NSData temp = NSData.FromStream(memoryStream) ?? new NSData(); - return UIImage.LoadFromData(temp); } + static UIImage? GetBitmapFromUrl(string? resource) { if (string.IsNullOrEmpty(resource)) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index a06e929358..aec685e9af 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -681,10 +681,10 @@ void StatusChanged(NSObservedChange obj) MediaElement.CurrentStateChanged(newState); } - void TimeControlStatusChanged(NSObservedChange obj) + async void TimeControlStatusChanged(NSObservedChange obj) { if (Player is null || Player.Status is AVPlayerStatus.Unknown - || Player.CurrentItem?.Error is not null) + || Player.CurrentItem?.Error is not null || metaData is null) { return; } @@ -696,8 +696,8 @@ void TimeControlStatusChanged(NSObservedChange obj) AVPlayerTimeControlStatus.WaitingToPlayAtSpecifiedRate => MediaElementState.Buffering, _ => MediaElement.CurrentState }; - - metaData?.SetMetadata(PlayerItem, MediaElement); + CancellationToken cancellationToken = new(); + await metaData.SetMetadata(PlayerItem, MediaElement, cancellationToken); MediaElement.CurrentStateChanged(newState); } From 2e1c4f662305a2eb19eeefbb4acc022c859b9194 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:26:36 -0800 Subject: [PATCH 29/44] Update src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/MediaManager.macios.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index aec685e9af..eb949ca389 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -230,7 +230,7 @@ protected virtual async partial ValueTask PlatformUpdateSource() { if (isFileSource) { - asset = AVAsset.FromUrl(NSUrl.FromFilename(source)); + asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(source)); } else { From 6061f5731d9bdc7f413b8f2f1e4a133c0c2fdb82 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:49:36 -0800 Subject: [PATCH 30/44] Update src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/MediaManager.macios.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index eb949ca389..a1432f0a92 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -696,7 +696,7 @@ async void TimeControlStatusChanged(NSObservedChange obj) AVPlayerTimeControlStatus.WaitingToPlayAtSpecifiedRate => MediaElementState.Buffering, _ => MediaElement.CurrentState }; - CancellationToken cancellationToken = new(); + var cancellationToken = CancellationToken.None; await metaData.SetMetadata(PlayerItem, MediaElement, cancellationToken); MediaElement.CurrentStateChanged(newState); From 531a4d1d7e3eceaf4a499eed286cbc27776ab36f Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:50:46 -0800 Subject: [PATCH 31/44] Update src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/MediaManager.android.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 2256828a77..8c46d98551 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -579,13 +579,9 @@ void StopService(in BoundServiceConnection boundServiceConnection) switch (mediaSource) { case UriMediaSource uriMediaSource: - { - return uriMediaSource.Uri?.AbsoluteUri; - } + return uriMediaSource.Uri?.AbsoluteUri; case FileMediaSource fileMediaSource: - { - return fileMediaSource.Path; - } + return fileMediaSource.Path; case ResourceMediaSource resourceMediaSource: { var package = PlayerView?.Context?.PackageName ?? ""; From 7d1224a3a3b559278c010d8526de81bb002b5b84 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:51:10 -0800 Subject: [PATCH 32/44] Update src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/MediaManager.android.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 8c46d98551..1614ebbe97 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -623,32 +623,26 @@ void StopService(in BoundServiceConnection boundServiceConnection) switch (mediaSource) { case FileMediaSource fileMediaSource: - { var filePath = fileMediaSource.Path; if (filePath is null || string.IsNullOrWhiteSpace(filePath)) { return null; } return await GetByteArrayFromFile(filePath, cancellationToken).ConfigureAwait(false); - } case ResourceMediaSource resourceMediaSource: - { var resource = resourceMediaSource.Path; if (resource is null || string.IsNullOrWhiteSpace(resource)) { return null; } return await GetMauiAssetBytes(resource, cancellationToken).ConfigureAwait(false); - } case UriMediaSource uriMediaSource: - { var url = uriMediaSource.Uri?.AbsoluteUri; if (url is null || string.IsNullOrWhiteSpace(url)) { return null; } return await GetBytesFromMetadataArtworkUrl(url, cancellationToken).ConfigureAwait(false); - } default: return null; } } From 9b0070395317e6ab49db46d34fb602627d306e4e Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:51:24 -0800 Subject: [PATCH 33/44] Update src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Primitives/Metadata.macios.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index ce6cd294a8..81136746f4 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -239,7 +239,7 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) using var memoryStream = new MemoryStream(); if (inputStream is null) { - System.Diagnostics.Trace.WriteLine($"{inputStream} is null."); + System.Diagnostics.Trace.WriteLine($"Failed to open app package file: '{resource}' - stream is null."); return null; } await inputStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); From dd89258bdb85480572427e9f7a339c2aaca604e7 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:52:26 -0800 Subject: [PATCH 34/44] Update src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/MediaManager.android.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 1614ebbe97..241a3ffcfb 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -594,7 +594,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) break; } default: - throw new NotSupportedException($"{MediaElement.Source?.GetType().FullName} is not yet supported for {nameof(MediaElement.Source)}"); + throw new NotSupportedException($"{mediaSource?.GetType().FullName} is not a supported MediaSource type"); } return null; } From 7cee93932a3e326adb460ab87ba5eaa639127e1e Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:55:02 -0800 Subject: [PATCH 35/44] Add check for Android foreground service enable flag StartService now verifies isAndroidForegroundServiceEnabled before starting the foreground service, preventing unnecessary service startup when the feature is disabled. --- .../Views/MediaManager.android.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 241a3ffcfb..0a52d2c01f 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -543,9 +543,12 @@ protected override void Dispose(bool disposing) } } - [MemberNotNull(nameof(connection))] void StartService() { + if (!isAndroidForegroundServiceEnabled) + { + return; + } var intent = new Intent(Android.App.Application.Context, typeof(MediaControlsService)); connection = new BoundServiceConnection(this); connection.MediaControlsServiceTaskRemoved += HandleMediaControlsServiceTaskRemoved; From d56063bcdfe9c2358faa037da141b741c7d30cf3 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 14:58:40 -0800 Subject: [PATCH 36/44] Improve error logging in file picker exception handler Updated the logging statement to include the exception object using the "{Ex}" placeholder, providing more detailed error information when file picking fails. --- .../Pages/Views/MediaElement/MediaElementPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index b37db46a05..af9a40eb27 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -322,7 +322,7 @@ async void DisplayPopup(object? sender, EventArgs? e) catch (Exception ex) { // The user canceled or something went wrong - logger.LogError(ex, "Error picking file"); + logger.LogError(ex, "Error picking file{Ex}", ex); } return null; From 63a24d8da6a2b73f63a440467dab12391aa0bc18 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 15:11:16 -0800 Subject: [PATCH 37/44] Fix ToggleCommand logic, update logging, remove file path IO Correct ToggleCommand null check, clarify NSData log message, and remove file path/asset stream handling in MediaManager.android.cs for simplified stream logic. --- .../Primitives/Metadata.macios.cs | 4 +- .../Views/MediaManager.android.cs | 38 ------------------- 2 files changed, 2 insertions(+), 40 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index 81136746f4..c014b7ffec 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -157,7 +157,7 @@ MPRemoteCommandHandlerStatus PauseCommand(MPRemoteCommandEvent? commandEvent) MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) { - if (commandEvent is null) + if (commandEvent is not null) { return MPRemoteCommandHandlerStatus.CommandFailed; } @@ -247,7 +247,7 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) NSData? nsdata = NSData.FromStream(memoryStream); if (nsdata is null) { - System.Diagnostics.Trace.TraceInformation($"{nsdata} is null."); + System.Diagnostics.Trace.TraceInformation($"NSData create from stream: {nsdata} is null."); return null; } return UIImage.LoadFromData(nsdata); diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 0a52d2c01f..2d0c7f8be2 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -676,22 +676,6 @@ void StopService(in BoundServiceConnection boundServiceConnection) var response = await client.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null; } - // Absolute File Path - else if (uri is not null && uri.Scheme == Uri.UriSchemeFile) - { - var normalizedFilePath = NormalizeFilePath(url); - - stream = File.Open(normalizedFilePath, FileMode.Open); - contentLength = await GetByteCountFromStream(stream, cancellationToken); - } - // Relative File Path - else if (Uri.TryCreate(url, UriKind.Relative, out _)) - { - var normalizedFilePath = NormalizeFilePath(url); - - stream = Platform.AppContext.Assets?.Open(normalizedFilePath) ?? throw new InvalidOperationException("Assets cannot be null"); - contentLength = await GetByteCountFromStream(stream, cancellationToken); - } if (stream is not null) { @@ -720,28 +704,6 @@ void StopService(in BoundServiceConnection boundServiceConnection) await stream.DisposeAsync(); } } - - static string NormalizeFilePath(string filePath) => filePath.Replace('\\', System.IO.Path.DirectorySeparatorChar).Replace('/', System.IO.Path.DirectorySeparatorChar); - - static async ValueTask GetByteCountFromStream(Stream stream, CancellationToken token) - { - if (stream.CanSeek) - { - return stream.Length; - } - - long countedStreamBytes = 0; - - var buffer = new byte[8192]; - int bytesRead; - - while ((bytesRead = await stream.ReadAsync(buffer, token)) > 0) - { - countedStreamBytes += bytesRead; - } - - return countedStreamBytes; - } } static async Task GetByteArrayFromFile(string filePath, CancellationToken cancellationToken = default) From f2bfc77b708ef2261fe7448c3546d6cfc25b94a5 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 15:14:50 -0800 Subject: [PATCH 38/44] Remove CancellationToken from SetMetadata call SetMetadata is now called without a CancellationToken, reflecting a change in its method signature that no longer requires this parameter. --- .../Views/MediaManager.macios.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index a1432f0a92..1f094fe92b 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -696,8 +696,7 @@ async void TimeControlStatusChanged(NSObservedChange obj) AVPlayerTimeControlStatus.WaitingToPlayAtSpecifiedRate => MediaElementState.Buffering, _ => MediaElement.CurrentState }; - var cancellationToken = CancellationToken.None; - await metaData.SetMetadata(PlayerItem, MediaElement, cancellationToken); + await metaData.SetMetadata(PlayerItem, MediaElement); MediaElement.CurrentStateChanged(newState); } From f5104c25766eabf45925f6ecd6a467f748525d72 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 15:21:28 -0800 Subject: [PATCH 39/44] Update samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Pages/Views/MediaElement/MediaElementPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index af9a40eb27..b37db46a05 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -322,7 +322,7 @@ async void DisplayPopup(object? sender, EventArgs? e) catch (Exception ex) { // The user canceled or something went wrong - logger.LogError(ex, "Error picking file{Ex}", ex); + logger.LogError(ex, "Error picking file"); } return null; From de54a84ccc1f9ae4804c942439504a908fb0f80a Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 15:26:26 -0800 Subject: [PATCH 40/44] Update src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Primitives/Metadata.macios.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index c014b7ffec..03fe5c263d 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -227,7 +227,30 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) { return null; } - return UIImage.LoadFromData(NSData.FromUrl(new NSUrl(resource))); + + try + { + var nsUrl = new NSUrl(resource); + NSData? data = NSData.FromUrl(nsUrl); + if (data is null) + { + System.Diagnostics.Trace.WriteLine($"Failed to load metadata artwork from URL: '{resource}' - NSData.FromUrl returned null."); + return null; + } + + UIImage? image = UIImage.LoadFromData(data); + if (image is null) + { + System.Diagnostics.Trace.WriteLine($"Failed to create UIImage from URL: '{resource}' - UIImage.LoadFromData returned null."); + } + + return image; + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine($"Error loading metadata artwork from URL '{resource}': {ex}"); + return null; + } } static async Task GetBitmapFromResource(string? resource, CancellationToken cancellationToken = default) { From 1d6d224b9cfc9a7671a32850be8cf95eb687c4b8 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 15:38:57 -0800 Subject: [PATCH 41/44] Refactor metadata artwork handling and logging Removed null-conditional logging for Logger, assuming it is always non-null. Moved thumbnail and poster clearing logic outside the switch statement to ensure artwork is reset when source, stream, or uri are null. This improves UI consistency when no artwork is available. --- .../Views/MediaManager.windows.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs index f4b6fb81ef..37c277d5a8 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.windows.cs @@ -423,7 +423,7 @@ async ValueTask UpdateMetadata() } else { - Logger?.LogWarning("UriMediaSource metadata artwork source is null, empty, or invalid."); + Logger.LogWarning("UriMediaSource metadata artwork source is null, empty, or invalid."); } break; case FileMediaSource: @@ -439,7 +439,7 @@ async ValueTask UpdateMetadata() { if (string.IsNullOrEmpty(source)) { - Logger?.LogWarning("ResourceMediaSource metadata artwork source path is null or empty."); + Logger.LogWarning("ResourceMediaSource metadata artwork source path is null or empty."); return; } string path = GetFullAppPackageFilePath(source); @@ -449,13 +449,9 @@ async ValueTask UpdateMetadata() } catch (FileNotFoundException e) { - Logger?.LogWarning("ResourceMediaSource file not found: {Message}", e.Message); + Logger.LogWarning("ResourceMediaSource file not found: {Message}", e.Message); } break; - case null: - systemMediaControls.DisplayUpdater.Thumbnail = null; - Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage()); - break; } if (source is not null && stream is not null && uri is not null) @@ -464,6 +460,12 @@ async ValueTask UpdateMetadata() Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage(uri)); } + else + { + systemMediaControls.DisplayUpdater.Thumbnail = null; + Dispatcher.Dispatch(() => Player.PosterSource = new BitmapImage()); + } + systemMediaControls.DisplayUpdater.Type = MediaPlaybackType.Music; systemMediaControls.DisplayUpdater.MusicProperties.Artist = MediaElement.MetadataArtist; systemMediaControls.DisplayUpdater.MusicProperties.Title = MediaElement.MetadataTitle; From 0d6ae86a71a1afb1c5d3345f9cc6c582bc86dd16 Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 15:41:53 -0800 Subject: [PATCH 42/44] Add null check before accessing url.AbsoluteString Prevent possible NullReferenceException by adding a null-conditional check when accessing url.AbsoluteString after calling NSBundle.MainBundle.GetUrlForResource. This ensures the code safely handles cases where the returned url may be null. --- .../Views/MediaManager.macios.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs index 1f094fe92b..962d50ee85 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.macios.cs @@ -462,7 +462,7 @@ protected virtual void Dispose(bool disposing) string extension = Path.GetExtension(path)[1..]; var url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory); - if (!string.IsNullOrEmpty(url.AbsoluteString)) + if (!string.IsNullOrEmpty(url?.AbsoluteString)) { return (url.AbsoluteString, false); } From 08bcbe27fb6abfcc6b909462bae75dd32989d6bd Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 15:58:13 -0800 Subject: [PATCH 43/44] Make HttpClient and artwork methods instance-based Converted HttpClient from static to instance member in MediaManager. Updated GetArtworkFromMediasource and GetBytesFromMetadataArtworkUrl to instance methods, ensuring each MediaManager has its own HttpClient. --- .../Views/MediaManager.android.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs index 2d0c7f8be2..688ed7ce06 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs @@ -23,7 +23,7 @@ public partial class MediaManager : Java.Lang.Object, IPlayerListener const int readyState = 3; const int endedState = 4; - static readonly HttpClient client = new(); + readonly HttpClient client = new(); readonly SemaphoreSlim seekToSemaphoreSlim = new(1, 1); double? previousSpeed; @@ -617,7 +617,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) return mediaItem; } - static async Task GetArtworkFromMediasource(MediaSource? mediaSource, CancellationToken cancellationToken = default) + async Task GetArtworkFromMediasource(MediaSource? mediaSource, CancellationToken cancellationToken = default) { if (mediaSource is null) { @@ -650,7 +650,7 @@ void StopService(in BoundServiceConnection boundServiceConnection) } } - static async Task GetBytesFromMetadataArtworkUrl(string url, CancellationToken cancellationToken = default) + async Task GetBytesFromMetadataArtworkUrl(string url, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(url)) { From 162679bde96e2bf7f48e9b72150fe314dac2c9fc Mon Sep 17 00:00:00 2001 From: James Crutchley Date: Wed, 18 Feb 2026 16:02:44 -0800 Subject: [PATCH 44/44] Add null/empty check for artwork file path in metadata Added validation to ensure the artwork file path is not null or empty before loading the bitmap. Logs a trace message and returns null if the path is invalid, preventing potential errors. --- .../Primitives/Metadata.macios.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs index 03fe5c263d..e9124eb210 100644 --- a/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs +++ b/src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs @@ -195,6 +195,11 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent) static async Task GetBitmapFromFile(string? resource, CancellationToken cancellationToken = default) { + if (string.IsNullOrEmpty(resource)) + { + System.Diagnostics.Trace.WriteLine("Metadata artwork file path is null or empty."); + return null; + } if (!File.Exists(resource)) { System.Diagnostics.Trace.WriteLine($"Metadata artwork file not found: '{resource}'.");