Skip to content

Commit c370fe0

Browse files
committed
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.
1 parent e66f78e commit c370fe0

File tree

11 files changed

+389
-226
lines changed

11 files changed

+389
-226
lines changed

samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
x:Name="MediaElement"
3131
ShouldAutoPlay="True"
3232
Source="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
33-
MetadataArtworkUrl="https://lh3.googleusercontent.com/pw/AP1GczNRrebWCJvfdIau1EbsyyYiwAfwHS0JXjbioXvHqEwYIIdCzuLodQCZmA57GADIo5iB3yMMx3t_vsefbfoHwSg0jfUjIXaI83xpiih6d-oT7qD_slR0VgNtfAwJhDBU09kS5V2T5ZML-WWZn8IrjD4J-g=w1792-h1024-s-no-gm"
33+
MetadataArtworkSource="https://lh3.googleusercontent.com/pw/AP1GczNRrebWCJvfdIau1EbsyyYiwAfwHS0JXjbioXvHqEwYIIdCzuLodQCZmA57GADIo5iB3yMMx3t_vsefbfoHwSg0jfUjIXaI83xpiih6d-oT7qD_slR0VgNtfAwJhDBU09kS5V2T5ZML-WWZn8IrjD4J-g=w1792-h1024-s-no-gm"
3434
MetadataTitle="Big Buck Bunny"
3535
MetadataArtist="Blender Foundation"
3636
MediaEnded="OnMediaEnded"

samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public partial class MediaElementPage : BasePage<MediaElementViewModel>
1616
const string loadLocalResource = "Load Local Resource";
1717
const string resetSource = "Reset Source to null";
1818
const string loadMusic = "Load Music";
19-
19+
const string loadCustomMediaSource = "Load Custom Image Source";
20+
static readonly string saveDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
2021
const string buckBunnyMp4Url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
2122
const string botImageUrl = "https://lh3.googleusercontent.com/pw/AP1GczNRrebWCJvfdIau1EbsyyYiwAfwHS0JXjbioXvHqEwYIIdCzuLodQCZmA57GADIo5iB3yMMx3t_vsefbfoHwSg0jfUjIXaI83xpiih6d-oT7qD_slR0VgNtfAwJhDBU09kS5V2T5ZML-WWZn8IrjD4J-g=w1792-h1024-s-no-gm";
2223
const string hlsStreamTestUrl = "https://mtoczko.github.io/hls-test-streams/test-gap/playlist.m3u8";
@@ -161,34 +162,34 @@ void Button_Clicked(object? sender, EventArgs e)
161162
async void ChangeSourceClicked(Object sender, EventArgs e)
162163
{
163164
var result = await DisplayActionSheet("Choose a source", "Cancel", null,
164-
loadOnlineMp4, loadHls, loadLocalResource, resetSource, loadMusic);
165+
loadOnlineMp4, loadHls, loadLocalResource, resetSource, loadMusic, loadCustomMediaSource);
165166

166167
switch (result)
167168
{
168169
case loadOnlineMp4:
169170
MediaElement.MetadataTitle = "Big Buck Bunny";
170-
MediaElement.MetadataArtworkUrl = botImageUrl;
171+
MediaElement.MetadataArtworkSource = MediaSource.FromUri(botImageUrl);
171172
MediaElement.MetadataArtist = "Big Buck Bunny Album";
172173
MediaElement.Source =
173174
MediaSource.FromUri(buckBunnyMp4Url);
174175
return;
175176

176177
case loadHls:
177178
MediaElement.MetadataArtist = "HLS Album";
178-
MediaElement.MetadataArtworkUrl = botImageUrl;
179+
MediaElement.MetadataArtworkSource = botImageUrl;
179180
MediaElement.MetadataTitle = "HLS Title";
180181
MediaElement.Source = MediaSource.FromUri(hlsStreamTestUrl);
181182
return;
182183

183184
case resetSource:
184-
MediaElement.MetadataArtworkUrl = string.Empty;
185+
MediaElement.MetadataArtworkSource = string.Empty;
185186
MediaElement.MetadataTitle = string.Empty;
186187
MediaElement.MetadataArtist = string.Empty;
187188
MediaElement.Source = null;
188189
return;
189190

190191
case loadLocalResource:
191-
MediaElement.MetadataArtworkUrl = botImageUrl;
192+
MediaElement.MetadataArtworkSource = MediaSource.FromResource("robot.jpg");
192193
MediaElement.MetadataTitle = "Local Resource Title";
193194
MediaElement.MetadataArtist = "Local Resource Album";
194195

@@ -210,10 +211,57 @@ async void ChangeSourceClicked(Object sender, EventArgs e)
210211
case loadMusic:
211212
MediaElement.MetadataTitle = "HAL 9000";
212213
MediaElement.MetadataArtist = "HAL 9000 Album";
213-
MediaElement.MetadataArtworkUrl = botImageUrl;
214+
MediaElement.MetadataArtworkSource = botImageUrl;
214215
MediaElement.Source = MediaSource.FromUri(hal9000AudioUrl);
215216
return;
217+
case loadCustomMediaSource:
218+
var fileresult = await PickAndShow(new PickOptions
219+
{
220+
PickerTitle = "Pick a media file",
221+
FileTypes = FilePickerFileType.Images,
222+
});
223+
var fileName = await Savefile(fileresult);
224+
if (fileName is not null)
225+
{
226+
MediaElement.MetadataArtworkSource = MediaSource.FromFile(fileName);
227+
MediaElement.MetadataTitle = "Big Buck Bunny";
228+
MediaElement.MetadataArtist = "Big Buck Bunny Album";
229+
MediaElement.Source =
230+
MediaSource.FromUri(buckBunnyMp4Url);
231+
}
232+
233+
return;
234+
}
235+
}
236+
static async Task<string?> Savefile(FileResult? fileresult)
237+
{
238+
if (fileresult is null)
239+
{
240+
System.Diagnostics.Trace.WriteLine("File result is null");
241+
return null;
242+
}
243+
try
244+
{
245+
using Stream fileStream = await fileresult.OpenReadAsync();
246+
using StreamReader reader = new(fileStream);
247+
var fileName = GetFileName(fileresult.FileName);
248+
using FileStream output = File.Create(fileName);
249+
await fileStream.CopyToAsync(output);
250+
fileStream.Seek(0, SeekOrigin.Begin);
251+
FileStream.Synchronized(output);
252+
return fileName;
253+
}
254+
catch (Exception ex)
255+
{
256+
System.Diagnostics.Trace.WriteLine(ex.Message);
257+
return null;
216258
}
259+
260+
}
261+
static string GetFileName(string name)
262+
{
263+
var filename = Path.GetFileName(name);
264+
return Path.Combine(saveDirectory, filename);
217265
}
218266

219267
async void ChangeAspectClicked(object? sender, EventArgs e)
@@ -274,4 +322,24 @@ void DisplayPopup(object sender, EventArgs e)
274322
popupMediaElement.Handler?.DisconnectHandler();
275323
};
276324
}
325+
static async Task<FileResult?> PickAndShow(PickOptions options)
326+
{
327+
try
328+
{
329+
var result = await FilePicker.Default.PickAsync(options);
330+
if (result is not null)
331+
{
332+
using var stream = await result.OpenReadAsync();
333+
var image = ImageSource.FromStream(() => stream);
334+
}
335+
336+
return result;
337+
}
338+
catch (Exception ex)
339+
{
340+
System.Diagnostics.Debug.WriteLine(ex.Message);
341+
}
342+
343+
return null;
344+
}
277345
}
Loading

src/CommunityToolkit.Maui.MediaElement/Interfaces/IMediaElement.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ public interface IMediaElement : IView, IAsynchronousMediaElementHandler
1919
string MetadataArtist { get; set; }
2020

2121
/// <summary>
22-
/// Gets or sets the artwork Image Url.
22+
/// Gets or sets the artwork Image source.
2323
/// </summary>
24-
string MetadataArtworkUrl { get; set; }
24+
public MediaSource? MetadataArtworkSource { get; set; }
2525

2626
/// <summary>
2727
/// Gets the media aspect ratio.

src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ public partial class MediaElement : View, IMediaElement, IDisposable
116116
public static readonly BindableProperty MetadataArtistProperty = BindableProperty.Create(nameof(MetadataArtist), typeof(string), typeof(MediaElement), string.Empty);
117117

118118
/// <summary>
119-
/// Backing store for the <see cref="MetadataArtworkUrl"/> property.
119+
/// Backing store for the <see cref="MetadataArtworkSource"/> property.
120120
/// </summary>
121-
public static readonly BindableProperty MetadataArtworkUrlProperty = BindableProperty.Create(nameof(MetadataArtworkUrl), typeof(string), typeof(MediaElement), string.Empty);
122-
121+
public static readonly BindableProperty MetadataArtworkSourceProperty = BindableProperty.Create(nameof(MetadataArtworkSource), typeof(MediaSource), typeof(MediaElement));
122+
123123
readonly WeakEventManager eventManager = new();
124124
readonly SemaphoreSlim seekToSemaphoreSlim = new(1, 1);
125125

@@ -362,13 +362,14 @@ public string MetadataArtist
362362
}
363363

364364
/// <summary>
365-
/// Gets or sets the Artwork Image Url of the media.
365+
/// Gets or sets the Artwork Image Source of the media.
366366
/// This is a bindable property.
367367
/// </summary>
368-
public string MetadataArtworkUrl
368+
[TypeConverter(typeof(MediaSourceConverter))]
369+
public MediaSource? MetadataArtworkSource
369370
{
370-
get => (string)GetValue(MetadataArtworkUrlProperty);
371-
set => SetValue(MetadataArtworkUrlProperty, value);
371+
get => (MediaSource)GetValue(MetadataArtworkSourceProperty);
372+
set => SetValue(MetadataArtworkSourceProperty, value);
372373
}
373374

374375
/// <summary>
@@ -577,7 +578,6 @@ void ClearTimer()
577578
timer.Stop();
578579
timer = null;
579580
}
580-
581581
void OnSourceChanged(object? sender, EventArgs eventArgs)
582582
{
583583
OnPropertyChanged(SourceProperty.PropertyName);

src/CommunityToolkit.Maui.MediaElement/Primitives/Metadata.macios.cs

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using AVFoundation;
2+
using CommunityToolkit.Maui.Views;
23
using CoreMedia;
34
using Foundation;
45
using MediaPlayer;
@@ -69,40 +70,32 @@ public Metadata(PlatformMediaElement player)
6970
/// </summary>
7071
/// <param name="playerItem"></param>
7172
/// <param name="mediaElement"></param>
72-
public void SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElement)
73+
public async Task SetMetadata(AVPlayerItem? playerItem, IMediaElement? mediaElement)
7374
{
7475
if (mediaElement is null)
7576
{
76-
Metadata.ClearNowPlaying();
7777
return;
7878
}
79+
ClearNowPlaying();
80+
var artwork = await MetadataArtworkUrl(mediaElement.MetadataArtworkSource).ConfigureAwait(false);
7981

82+
if (artwork is not null && artwork is UIImage image)
83+
{
84+
NowPlayingInfo.Artwork = new(boundsSize: new(320, 240), requestHandler: _ => image);
85+
}
86+
else
87+
{
88+
NowPlayingInfo.Artwork = new(boundsSize: new(0, 0), requestHandler: _ => defaultUIImage);
89+
}
8090
NowPlayingInfo.Title = mediaElement.MetadataTitle;
8191
NowPlayingInfo.Artist = mediaElement.MetadataArtist;
8292
NowPlayingInfo.PlaybackDuration = playerItem?.Duration.Seconds ?? 0;
8393
NowPlayingInfo.IsLiveStream = false;
8494
NowPlayingInfo.PlaybackRate = mediaElement.Speed;
8595
NowPlayingInfo.ElapsedPlaybackTime = playerItem?.CurrentTime.Seconds ?? 0;
86-
NowPlayingInfo.Artwork = new(boundsSize: new(320, 240), requestHandler: _ => GetImage(mediaElement.MetadataArtworkUrl));
8796
MPNowPlayingInfoCenter.DefaultCenter.NowPlaying = NowPlayingInfo;
8897
}
8998

90-
static UIImage GetImage(string imageUri)
91-
{
92-
try
93-
{
94-
if (imageUri.StartsWith(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
95-
{
96-
return UIImage.LoadFromData(NSData.FromUrl(new NSUrl(imageUri))) ?? defaultUIImage;
97-
}
98-
return defaultUIImage;
99-
}
100-
catch
101-
{
102-
return defaultUIImage;
103-
}
104-
}
105-
10699
MPRemoteCommandHandlerStatus SeekCommand(MPRemoteCommandEvent? commandEvent)
107100
{
108101
if (commandEvent is not MPChangePlaybackPositionCommandEvent eventArgs)
@@ -179,4 +172,70 @@ MPRemoteCommandHandlerStatus ToggleCommand(MPRemoteCommandEvent? commandEvent)
179172

180173
return MPRemoteCommandHandlerStatus.Success;
181174
}
175+
176+
public static async Task<UIImage?> MetadataArtworkUrl(MediaSource? artworkUrl, CancellationToken cancellationToken = default)
177+
{
178+
if (artworkUrl is UriMediaSource uriMediaSource)
179+
{
180+
var uri = uriMediaSource.Uri;
181+
return GetBitmapFromUrl(uri?.AbsoluteUri);
182+
}
183+
else if (artworkUrl is FileMediaSource fileMediaSource)
184+
{
185+
var uri = fileMediaSource.Path;
186+
187+
return await GetBitmapFromFile(uri, cancellationToken).ConfigureAwait(false);
188+
}
189+
else if (artworkUrl is ResourceMediaSource resourceMediaSource)
190+
{
191+
var path = resourceMediaSource.Path;
192+
return await GetBitmapFromResource(path, cancellationToken).ConfigureAwait(false);
193+
}
194+
return null;
195+
}
196+
197+
static async Task<UIImage?> GetBitmapFromFile(string? resource, CancellationToken cancellationToken = default)
198+
{
199+
if (string.IsNullOrWhiteSpace(resource))
200+
{
201+
return null;
202+
}
203+
using var fileStream = File.OpenRead(resource);
204+
using var memoryStream = new MemoryStream();
205+
await fileStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
206+
memoryStream.Position = 0;
207+
NSData temp = NSData.FromStream(memoryStream) ?? new NSData();
208+
return UIImage.LoadFromData(temp);
209+
}
210+
static UIImage? GetBitmapFromUrl(string? resource)
211+
{
212+
if (string.IsNullOrEmpty(resource))
213+
{
214+
return null;
215+
}
216+
return UIImage.LoadFromData(NSData.FromUrl(new NSUrl(resource)));
217+
}
218+
static async Task<UIImage?> GetBitmapFromResource(string? resource, CancellationToken cancellationToken = default)
219+
{
220+
if (string.IsNullOrWhiteSpace(resource))
221+
{
222+
return null;
223+
}
224+
using var inputStream = await FileSystem.OpenAppPackageFileAsync(resource).ConfigureAwait(false);
225+
using var memoryStream = new MemoryStream();
226+
if (inputStream is null)
227+
{
228+
System.Diagnostics.Trace.TraceError($"{inputStream} is null.");
229+
return null;
230+
}
231+
await inputStream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
232+
memoryStream.Position = 0;
233+
NSData? nsdata = NSData.FromStream(memoryStream);
234+
if (nsdata is null)
235+
{
236+
System.Diagnostics.Trace.TraceError($"{nsdata} is null.");
237+
return null;
238+
}
239+
return UIImage.LoadFromData(nsdata);
240+
}
182241
}

0 commit comments

Comments
 (0)