Skip to content

Commit 896e2be

Browse files
Popup: Added Audio and Video support (MAUI iOS and Windows) (#697)
* Add audio and video support in HtmlToView Implements handling for `source`, `Audio` and `Video` tags. * Added UI to play pause and reply the media in WPF Popup Media control. * Fixing WinUI Audio and Video markups * Moved Media UI to separate file * Missed file * Updated Audio nad Video markups processing to Block. Added Play/Pause, Time slider and time display to Popup playback controls. * Addressing code review comments * Adding Video and Audio support for iOS and Windows in MAUI. * Adding fallback text * Enhance MauiMediaElement with documentation and fixes Changed MauiMediaElement declaration to a partial class Enhanced the Dispose method to properly remove media items * Removed unnecessary namespaces * Remove unnecessary ConnectHandler methods * Add Android-specific handling for Audio and Video Support * Removing mediaType parsing * Adding else block for ADNROID check adn removing redundant condition on goto statement
1 parent 73fb767 commit 896e2be

File tree

6 files changed

+174
-3
lines changed

6 files changed

+174
-3
lines changed

src/Toolkit/Toolkit.Maui/AppHostBuilderExtensions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Esri.ArcGISRuntime.Toolkit.Maui.Internal;
2+
13
namespace Esri.ArcGISRuntime.Toolkit.Maui
24
{
35
/// <summary>
@@ -12,9 +14,13 @@ public static class AppHostBuilderExtensions
1214
/// <returns>The host builder</returns>
1315
public static MauiAppBuilder UseArcGISToolkit(this MauiAppBuilder builder)
1416
{
15-
return builder.ConfigureFonts(fonts => fonts
17+
#if WINDOWS || __IOS__
18+
builder.ConfigureMauiHandlers(handler => handler.AddHandler<MauiMediaElement, MauiMediaElementHandler>());
19+
#endif
20+
builder.ConfigureFonts(fonts => fonts
1621
.AddEmbeddedResourceFont(typeof(AppHostBuilderExtensions).Assembly, "toolkit-icons.ttf", ToolkitIcons.FontFamilyName)
1722
);
23+
return builder;
1824
}
1925
}
2026
}

src/Toolkit/Toolkit.Maui/Internal/HtmlToView.Maui.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// * limitations under the License.
1515
// ******************************************************************************/
1616

17-
17+
using Esri.ArcGISRuntime.Toolkit.Maui.Internal;
1818
using Esri.ArcGISRuntime.Toolkit.Maui.Primitives;
1919

2020
namespace Esri.ArcGISRuntime.Toolkit.Internal;
@@ -163,6 +163,41 @@ private static View CreateBlock(MarkupNode node, EventHandler<Uri>? urlClickHand
163163
imageElement.Source = imageSource;
164164
return imageElement;
165165

166+
case MarkupType.Audio:
167+
case MarkupType.Video:
168+
#if ANDROID // TODO: This needs to be revisited when we have a support for Android
169+
goto case MarkupType.Block;
170+
#else
171+
// Find the first valid <source> child, or use the node's Content
172+
string? mediaSrc = node.Content;
173+
foreach (var child in node.Children)
174+
{
175+
if (child.Type is MarkupType.Source && !string.IsNullOrEmpty(child.Content))
176+
{
177+
mediaSrc = child.Content;
178+
break;
179+
}
180+
}
181+
if (!string.IsNullOrEmpty(mediaSrc))
182+
{
183+
if (Uri.TryCreate(mediaSrc, UriKind.Absolute, out var mediaUri))
184+
{
185+
var mediaElement = new MauiMediaElement
186+
{
187+
Source = mediaUri,
188+
HorizontalOptions = LayoutOptions.Fill,
189+
VerticalOptions = LayoutOptions.Center,
190+
};
191+
return mediaElement;
192+
}
193+
else
194+
{
195+
return new Label { Text = "Invalid media URL" };
196+
}
197+
}
198+
return new Label { Text = "Media not available" };
199+
#endif
200+
166201
case MarkupType.Link:
167202
// If the link wraps block content (like <img>), render it as a tappable ContentView.
168203
if (Uri.TryCreate(node.Content, UriKind.Absolute, out var linkUri))
@@ -470,6 +505,6 @@ private static bool HasAnyBlocks(MarkupNode node)
470505

471506
private static bool MapsToBlock(MarkupNode node)
472507
{
473-
return node.Type is MarkupType.List or MarkupType.Table or MarkupType.Block or MarkupType.Divider or MarkupType.Image;
508+
return node.Type is MarkupType.List or MarkupType.Table or MarkupType.Block or MarkupType.Divider or MarkupType.Image or MarkupType.Audio or MarkupType.Video;
474509
}
475510
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Esri.ArcGISRuntime.Toolkit.Maui.Internal;
2+
3+
/// <summary>
4+
/// A view that displays audio or video media from a specified source.
5+
/// </summary>
6+
internal partial class MauiMediaElement : View
7+
{
8+
public static readonly BindableProperty SourceProperty =
9+
BindableProperty.Create(nameof(Source), typeof(Uri), typeof(MauiMediaElement), null);
10+
11+
public Uri Source
12+
{
13+
get => (Uri)GetValue(SourceProperty);
14+
set => SetValue(SourceProperty, value);
15+
}
16+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#if WINDOWS
2+
using Microsoft.Maui.Handlers;
3+
using Microsoft.UI.Xaml.Controls;
4+
using Windows.Media.Core;
5+
6+
namespace Esri.ArcGISRuntime.Toolkit.Maui.Internal
7+
{
8+
internal partial class MauiMediaElementHandler : ViewHandler<MauiMediaElement, MediaPlayerElement>
9+
{
10+
protected override MediaPlayerElement CreatePlatformView() => new();
11+
12+
public void UpdateSource(Uri source)
13+
{
14+
if (source is not null && PlatformView is MediaPlayerElement platformView)
15+
{
16+
platformView.Source = MediaSource.CreateFromUri(source);
17+
platformView.AreTransportControlsEnabled = true;
18+
platformView.Stretch = Microsoft.UI.Xaml.Media.Stretch.Uniform;
19+
}
20+
}
21+
22+
protected override void DisconnectHandler(MediaPlayerElement platformView)
23+
{
24+
platformView.Source = null;
25+
base.DisconnectHandler(platformView);
26+
}
27+
}
28+
}
29+
#endif
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#if WINDOWS || __IOS__
2+
using Microsoft.Maui.Handlers;
3+
4+
namespace Esri.ArcGISRuntime.Toolkit.Maui.Internal
5+
{
6+
internal partial class MauiMediaElementHandler
7+
{
8+
public static readonly IPropertyMapper<MauiMediaElement, MauiMediaElementHandler> PropertyMapper =
9+
new PropertyMapper<MauiMediaElement, MauiMediaElementHandler>(ViewHandler.ViewMapper)
10+
{
11+
[nameof(MauiMediaElement.Source)] = MapSource,
12+
};
13+
14+
public MauiMediaElementHandler() : base(PropertyMapper)
15+
{
16+
}
17+
18+
private static void MapSource(MauiMediaElementHandler handler, MauiMediaElement view)
19+
{
20+
handler.UpdateSource(view.Source);
21+
}
22+
}
23+
}
24+
25+
#endif
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#if __IOS__
2+
using AVFoundation;
3+
using AVKit;
4+
using CoreGraphics;
5+
using Foundation;
6+
using Microsoft.Maui.Handlers;
7+
using UIKit;
8+
9+
namespace Esri.ArcGISRuntime.Toolkit.Maui.Internal;
10+
11+
internal partial class MauiMediaElementHandler : ViewHandler<MauiMediaElement, UIView>, IDisposable
12+
{
13+
private AVPlayer? _player;
14+
private AVPlayerViewController? _playerController;
15+
private UIView _playerView;
16+
17+
protected override UIView CreatePlatformView()
18+
{
19+
_player = new AVPlayer();
20+
_playerController = new AVPlayerViewController
21+
{
22+
Player = _player,
23+
};
24+
_playerView = _playerController.View;
25+
_playerView.Frame = new CGRect(0, 0, 320, 180); // Default size, The aspect ratio is still maintained if video has different dimensions
26+
27+
return _playerView;
28+
}
29+
30+
public void UpdateSource(Uri source)
31+
{
32+
if (source is not null)
33+
{
34+
var url = NSUrl.FromString(source.ToString());
35+
_player.ReplaceCurrentItemWithPlayerItem(new AVPlayerItem(url));
36+
}
37+
}
38+
39+
protected override void DisconnectHandler(UIView platformView)
40+
{
41+
Dispose();
42+
base.DisconnectHandler(platformView);
43+
}
44+
45+
public void Dispose()
46+
{
47+
// Cleanup resources
48+
_player?.Pause();
49+
_player.ReplaceCurrentItemWithPlayerItem(null);
50+
_player?.Dispose();
51+
_player = null;
52+
_playerController?.Dispose();
53+
_playerController = null;
54+
_playerView?.RemoveFromSuperview();
55+
_playerView?.Dispose();
56+
_playerView = null;
57+
}
58+
}
59+
60+
#endif

0 commit comments

Comments
 (0)