Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ namespace CommunityToolkit.Maui.Sample.Pages.Views;

public partial class MediaElementPage : BasePage<MediaElementViewModel>
{
readonly ILogger logger;

const string loadOnlineMp4 = "Load Online MP4";
const string loadHls = "Load HTTP Live Stream (HLS)";
const string loadLocalResource = "Load Local Resource";
Expand All @@ -22,11 +20,18 @@ public partial class MediaElementPage : BasePage<MediaElementViewModel>
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";

public MediaElementPage(MediaElementViewModel viewModel, ILogger<MediaElementPage> logger) : base(viewModel)

readonly ILogger logger;
readonly IDeviceInfo deviceInfo;
readonly IFileSystem fileSystem;

public MediaElementPage(MediaElementViewModel viewModel, IFileSystem fileSystem, IDeviceInfo deviceInfo, ILogger<MediaElementPage> logger) : base(viewModel)
{
InitializeComponent();

this.logger = logger;
this.deviceInfo = deviceInfo;
this.fileSystem = fileSystem;
MediaElement.PropertyChanged += MediaElement_PropertyChanged;
}

Expand Down Expand Up @@ -163,6 +168,9 @@ async void ChangeSourceClicked(Object sender, EventArgs e)
var result = await DisplayActionSheet("Choose a source", "Cancel", null,
loadOnlineMp4, loadHls, loadLocalResource, resetSource, loadMusic);

MediaElement.Stop();
MediaElement.Source = null;

switch (result)
{
case loadOnlineMp4:
Expand Down Expand Up @@ -246,15 +254,36 @@ async void ChangeAspectClicked(object? sender, EventArgs e)
async void DisplayPopup(object sender, EventArgs e)
{
MediaElement.Pause();

MediaSource source;

if (deviceInfo.Platform == DevicePlatform.Android)
{
source = MediaSource.FromResource("AndroidVideo.mp4");
}
else if (deviceInfo.Platform == DevicePlatform.MacCatalyst
|| deviceInfo.Platform == DevicePlatform.iOS
|| deviceInfo.Platform == DevicePlatform.macOS)
{
source = MediaSource.FromResource("AppleVideo.mp4");
}
else
{
source = MediaSource.FromResource("WindowsVideo.mp4");
}

var popupMediaElement = new MediaElement
{
AndroidViewType = AndroidViewType.SurfaceView,
Source = MediaSource.FromResource("AppleVideo.mp4"),
Source = source,
MetadataArtworkUrl = "dotnet_bot.png",
ShouldAutoPlay = true,
ShouldShowPlaybackControls = true,
};

await this.ShowPopupAsync(popupMediaElement);

popupMediaElement.Stop();
popupMediaElement.Source = null;
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 88 additions & 14 deletions src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Android.Content;
using Android.Views;
using Android.Widget;
Expand Down Expand Up @@ -536,27 +537,97 @@ protected override void Dispose(bool disposing)
}
}

static async Task<byte[]> GetBytesFromMetadataArtworkUrl(string? url, CancellationToken cancellationToken = default)
static async Task<byte[]> GetBytesFromMetadataArtworkUrl(string url, CancellationToken cancellationToken = default)
{
byte[] artworkData = [];
if (string.IsNullOrWhiteSpace(url))
{
return [];
}

Stream? stream = null;
Uri.TryCreate(url, UriKind.Absolute, out var uri);

try
{
var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false);
var stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null;
byte[] artworkData = [];
long? contentLength = null;

// HTTP or HTTPS URL
if (uri is not null &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
{
var request = new HttpRequestMessage(HttpMethod.Head, url);
var contentLengthResponse = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
contentLength = contentLengthResponse.Content.Headers.ContentLength ?? 0;

if (stream is null)
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)
{
return artworkData;
var normalizedFilePath = NormalizeFilePath(url);

stream = File.Open(normalizedFilePath, FileMode.Create);
contentLength = await GetByteCountFromStream(stream, cancellationToken);
}
// Relative File Path
else if (Uri.TryCreate(url, UriKind.Relative, out _))
{
var normalizedFilePath = NormalizeFilePath(url);

using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
var bytes = memoryStream.ToArray();
return bytes;
stream = Platform.AppContext.Assets?.Open(normalizedFilePath) ?? throw new InvalidOperationException("Assets cannot be null");
contentLength = await GetByteCountFromStream(stream, cancellationToken);
}

if (stream is not null)
{
if (!contentLength.HasValue)
{
throw new InvalidOperationException($"{nameof(contentLength)} must be set when {nameof(stream)} is not null");
}

artworkData = new byte[contentLength.Value];
using var memoryStream = new MemoryStream(artworkData);
await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
}

return artworkData;
}
catch
catch (Exception e)
{
return artworkData;
Trace.WriteLine($"Unable to retrieve {nameof(MediaElement.MetadataArtworkUrl)} for {url}.{e}\n");
return [];
}
finally
{
if (stream is not null)
{
stream.Close();
await stream.DisposeAsync();
}
}

static string NormalizeFilePath(string filePath) => filePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);

static async ValueTask<long> 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;
}
}

Expand Down Expand Up @@ -636,7 +707,10 @@ void StopService(in BoundServiceConnection boundServiceConnection)
mediaMetaData.SetArtist(MediaElement.MetadataArtist);
mediaMetaData.SetTitle(MediaElement.MetadataTitle);
var data = await GetBytesFromMetadataArtworkUrl(MediaElement.MetadataArtworkUrl, cancellationToken).ConfigureAwait(true);
mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover);
if (data is not null && data.Length > 0)
{
mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover);
}

mediaItem = new MediaItem.Builder();
mediaItem.SetUri(url);
Expand Down
Loading