Skip to content

Commit 63ed8c2

Browse files
Merge branch 'main' into Refactor-CameraView.CaptureImage,-StartCameraPreview-and-StopCameraPreview
2 parents 8996040 + d7ab26b commit 63ed8c2

File tree

7 files changed

+172
-41
lines changed

7 files changed

+172
-41
lines changed

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ namespace CommunityToolkit.Maui.Sample.Pages.Views;
1010

1111
public partial class MediaElementPage : BasePage<MediaElementViewModel>
1212
{
13-
readonly ILogger logger;
14-
1513
const string loadOnlineMp4 = "Load Online MP4";
1614
const string loadHls = "Load HTTP Live Stream (HLS)";
1715
const string loadLocalResource = "Load Local Resource";
@@ -22,11 +20,18 @@ public partial class MediaElementPage : BasePage<MediaElementViewModel>
2220
const string hlsStreamTestUrl = "https://mtoczko.github.io/hls-test-streams/test-gap/playlist.m3u8";
2321
const string hal9000AudioUrl = "https://github.com/prof3ssorSt3v3/media-sample-files/raw/master/hal-9000.mp3";
2422

25-
public MediaElementPage(MediaElementViewModel viewModel, ILogger<MediaElementPage> logger) : base(viewModel)
23+
24+
readonly ILogger logger;
25+
readonly IDeviceInfo deviceInfo;
26+
readonly IFileSystem fileSystem;
27+
28+
public MediaElementPage(MediaElementViewModel viewModel, IFileSystem fileSystem, IDeviceInfo deviceInfo, ILogger<MediaElementPage> logger) : base(viewModel)
2629
{
2730
InitializeComponent();
2831

2932
this.logger = logger;
33+
this.deviceInfo = deviceInfo;
34+
this.fileSystem = fileSystem;
3035
MediaElement.PropertyChanged += MediaElement_PropertyChanged;
3136
}
3237

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

171+
MediaElement.Stop();
172+
MediaElement.Source = null;
173+
166174
switch (result)
167175
{
168176
case loadOnlineMp4:
@@ -246,15 +254,36 @@ async void ChangeAspectClicked(object? sender, EventArgs e)
246254
async void DisplayPopup(object sender, EventArgs e)
247255
{
248256
MediaElement.Pause();
257+
258+
MediaSource source;
259+
260+
if (deviceInfo.Platform == DevicePlatform.Android)
261+
{
262+
source = MediaSource.FromResource("AndroidVideo.mp4");
263+
}
264+
else if (deviceInfo.Platform == DevicePlatform.MacCatalyst
265+
|| deviceInfo.Platform == DevicePlatform.iOS
266+
|| deviceInfo.Platform == DevicePlatform.macOS)
267+
{
268+
source = MediaSource.FromResource("AppleVideo.mp4");
269+
}
270+
else
271+
{
272+
source = MediaSource.FromResource("WindowsVideo.mp4");
273+
}
274+
249275
var popupMediaElement = new MediaElement
250276
{
251277
AndroidViewType = AndroidViewType.SurfaceView,
252-
Source = MediaSource.FromResource("AppleVideo.mp4"),
278+
Source = source,
279+
MetadataArtworkUrl = "dotnet_bot.png",
253280
ShouldAutoPlay = true,
254281
ShouldShowPlaybackControls = true,
255282
};
256-
283+
257284
await this.ShowPopupAsync(popupMediaElement);
285+
258286
popupMediaElement.Stop();
287+
popupMediaElement.Source = null;
259288
}
260289
}

samples/CommunityToolkit.Maui.Sample/Pages/Views/Popup/PopupsPage.xaml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ async void HandleSelfClosingPopupButtonClicked(object? sender, EventArgs e)
125125
{
126126
CanBeDismissedByTappingOutsideOfPopup = false
127127
});
128-
128+
129129
await Task.Delay(TimeSpan.FromSeconds(2));
130-
130+
131131
await this.ClosePopupAsync();
132132
}
133133
}
58.4 KB
Loading

src/CommunityToolkit.Maui.MediaElement/Views/MauiMediaElement.android.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ void SetSystemBarsVisibility()
180180
}
181181
else
182182
{
183-
WindowCompat.SetDecorFitsSystemWindows(currentWindow, true);
184183
if (OperatingSystem.IsAndroidVersionAtLeast(30))
185184
{
186185
if (isSystemBarVisible)
@@ -195,6 +194,7 @@ void SetSystemBarsVisibility()
195194

196195
windowInsetsControllerCompat.Show(barTypes);
197196
windowInsetsControllerCompat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorDefault;
197+
WindowCompat.SetDecorFitsSystemWindows(currentWindow, true);
198198
}
199199
}
200200

src/CommunityToolkit.Maui.MediaElement/Views/MauiMediaElement.macios.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ static bool TryGetCurrentPage([NotNullWhen(true)] out Page? currentPage)
170170
}
171171

172172
// If not using Shell or a Modal Page, return the visible page in the (non-modal) NavigationStack
173-
if (window.Navigation.NavigationStack[^1] is Page page)
173+
if (window.Navigation.NavigationStack.LastOrDefault() is Page page)
174174
{
175175
currentPage = page;
176176
return true;

src/CommunityToolkit.Maui.MediaElement/Views/MediaManager.android.cs

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Diagnostics;
2+
using System.Diagnostics.CodeAnalysis;
23
using Android.Content;
34
using Android.Views;
45
using Android.Widget;
@@ -536,27 +537,97 @@ protected override void Dispose(bool disposing)
536537
}
537538
}
538539

539-
static async Task<byte[]> GetBytesFromMetadataArtworkUrl(string? url, CancellationToken cancellationToken = default)
540+
static async Task<byte[]> GetBytesFromMetadataArtworkUrl(string url, CancellationToken cancellationToken = default)
540541
{
541-
byte[] artworkData = [];
542+
if (string.IsNullOrWhiteSpace(url))
543+
{
544+
return [];
545+
}
546+
547+
Stream? stream = null;
548+
Uri.TryCreate(url, UriKind.Absolute, out var uri);
549+
542550
try
543551
{
544-
var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false);
545-
var stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null;
552+
byte[] artworkData = [];
553+
long? contentLength = null;
554+
555+
// HTTP or HTTPS URL
556+
if (uri is not null &&
557+
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
558+
{
559+
var request = new HttpRequestMessage(HttpMethod.Head, url);
560+
var contentLengthResponse = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
561+
contentLength = contentLengthResponse.Content.Headers.ContentLength ?? 0;
546562

547-
if (stream is null)
563+
var response = await client.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
564+
stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null;
565+
}
566+
// Absolute File Path
567+
else if (uri is not null && uri.Scheme == Uri.UriSchemeFile)
548568
{
549-
return artworkData;
569+
var normalizedFilePath = NormalizeFilePath(url);
570+
571+
stream = File.Open(normalizedFilePath, FileMode.Create);
572+
contentLength = await GetByteCountFromStream(stream, cancellationToken);
550573
}
574+
// Relative File Path
575+
else if (Uri.TryCreate(url, UriKind.Relative, out _))
576+
{
577+
var normalizedFilePath = NormalizeFilePath(url);
551578

552-
using var memoryStream = new MemoryStream();
553-
await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
554-
var bytes = memoryStream.ToArray();
555-
return bytes;
579+
stream = Platform.AppContext.Assets?.Open(normalizedFilePath) ?? throw new InvalidOperationException("Assets cannot be null");
580+
contentLength = await GetByteCountFromStream(stream, cancellationToken);
581+
}
582+
583+
if (stream is not null)
584+
{
585+
if (!contentLength.HasValue)
586+
{
587+
throw new InvalidOperationException($"{nameof(contentLength)} must be set when {nameof(stream)} is not null");
588+
}
589+
590+
artworkData = new byte[contentLength.Value];
591+
using var memoryStream = new MemoryStream(artworkData);
592+
await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
593+
}
594+
595+
return artworkData;
556596
}
557-
catch
597+
catch (Exception e)
558598
{
559-
return artworkData;
599+
Trace.WriteLine($"Unable to retrieve {nameof(MediaElement.MetadataArtworkUrl)} for {url}.{e}\n");
600+
return [];
601+
}
602+
finally
603+
{
604+
if (stream is not null)
605+
{
606+
stream.Close();
607+
await stream.DisposeAsync();
608+
}
609+
}
610+
611+
static string NormalizeFilePath(string filePath) => filePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
612+
613+
static async ValueTask<long> GetByteCountFromStream(Stream stream, CancellationToken token)
614+
{
615+
if (stream.CanSeek)
616+
{
617+
return stream.Length;
618+
}
619+
620+
long countedStreamBytes = 0;
621+
622+
var buffer = new byte[8192];
623+
int bytesRead;
624+
625+
while ((bytesRead = await stream.ReadAsync(buffer, token)) > 0)
626+
{
627+
countedStreamBytes += bytesRead;
628+
}
629+
630+
return countedStreamBytes;
560631
}
561632
}
562633

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

641715
mediaItem = new MediaItem.Builder();
642716
mediaItem.SetUri(url);

src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.windows.cs

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,8 @@ protected override void OnAttachedTo(View bindable, FrameworkElement platformVie
2727

2828
ApplyTintColor(platformView, bindable, TintColor);
2929

30+
PropertyChanged += OnIconTintColorBehaviorPropertyChanged;
3031
bindable.PropertyChanged += OnElementPropertyChanged;
31-
this.PropertyChanged += (s, e) =>
32-
{
33-
if (e.PropertyName == TintColorProperty.PropertyName)
34-
{
35-
if (currentColorBrush is not null && TintColor is not null)
36-
{
37-
currentColorBrush.Color = TintColor.ToWindowsColor();
38-
}
39-
else
40-
{
41-
ApplyTintColor(platformView, bindable, TintColor);
42-
}
43-
}
44-
};
4532
}
4633

4734
/// <inheritdoc/>
@@ -50,6 +37,8 @@ protected override void OnDetachedFrom(View bindable, FrameworkElement platformV
5037
base.OnDetachedFrom(bindable, platformView);
5138

5239
bindable.PropertyChanged -= OnElementPropertyChanged;
40+
PropertyChanged -= OnIconTintColorBehaviorPropertyChanged;
41+
5342
RemoveTintColor(platformView);
5443
}
5544

@@ -83,10 +72,34 @@ static bool TryGetSourceImageUri(WImage? imageControl, IImageElement? imageEleme
8372
return false;
8473
}
8574

75+
void OnIconTintColorBehaviorPropertyChanged(object? sender, PropertyChangedEventArgs e)
76+
{
77+
ArgumentNullException.ThrowIfNull(sender);
78+
var iconTintColorBehavior = (IconTintColorBehavior)sender;
79+
80+
if (iconTintColorBehavior.View is not IView bindable
81+
|| bindable.Handler?.PlatformView is not FrameworkElement platformView)
82+
{
83+
return;
84+
}
85+
86+
if (e.PropertyName == TintColorProperty.PropertyName)
87+
{
88+
if (currentColorBrush is not null && TintColor is not null)
89+
{
90+
currentColorBrush.Color = TintColor.ToWindowsColor();
91+
}
92+
else
93+
{
94+
ApplyTintColor(platformView, bindable, TintColor);
95+
}
96+
}
97+
}
98+
8699
void OnElementPropertyChanged(object? sender, PropertyChangedEventArgs e)
87100
{
88101
if (e.PropertyName is not string propertyName
89-
|| sender is not View bindable
102+
|| sender is not IView bindable
90103
|| bindable.Handler?.PlatformView is not FrameworkElement platformView)
91104
{
92105
return;
@@ -99,7 +112,7 @@ void OnElementPropertyChanged(object? sender, PropertyChangedEventArgs e)
99112
}
100113
}
101114

102-
void ApplyTintColor(FrameworkElement platformView, View element, Color? color)
115+
void ApplyTintColor(FrameworkElement platformView, IView element, Color? color)
103116
{
104117
RemoveTintColor(platformView);
105118

@@ -128,7 +141,7 @@ void ApplyTintColor(FrameworkElement platformView, View element, Color? color)
128141
}
129142
}
130143

131-
void LoadAndApplyImageTintColor(View element, WImage image, Color color)
144+
void LoadAndApplyImageTintColor(IView element, WImage image, Color color)
132145
{
133146
if (element is IImageElement { Source: UriImageSource uriImageSource })
134147
{
@@ -140,6 +153,21 @@ void LoadAndApplyImageTintColor(View element, WImage image, Color color)
140153

141154
ApplyTintColor();
142155
}
156+
else if (image.IsLoaded)
157+
{
158+
// Sometimes WImage source doesn't match View source so the image is not ready to be tinted
159+
// We must wait for next ImageOpened event
160+
if (element is IImageElement { Source: FileImageSource fileImageSource }
161+
&& image.Source is BitmapImage bitmapImage
162+
&& Uri.Compare(new Uri($"{bitmapImage.UriSource.Scheme}:///{fileImageSource.File}"), bitmapImage.UriSource, UriComponents.Path, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) is not 0)
163+
{
164+
image.ImageOpened += OnImageOpened;
165+
}
166+
else
167+
{
168+
ApplyTintColor();
169+
}
170+
}
143171
else
144172
{
145173
image.ImageOpened += OnImageOpened;
@@ -178,7 +206,7 @@ void OnImageSizeChanged(object sender, SizeChangedEventArgs e)
178206
}
179207
}
180208

181-
void ApplyImageTintColor(View element, WImage image, Color color)
209+
void ApplyImageTintColor(IView element, WImage image, Color color)
182210
{
183211
if (!TryGetSourceImageUri(image, (IImageElement)element, out var uri))
184212
{

0 commit comments

Comments
 (0)