Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
14eab7b
Add support to make AndroidForegroundService optional to MediaElement
ne0rrmatrix May 7, 2025
44a9928
Rename foreground service property for clarity
ne0rrmatrix May 7, 2025
c683d61
Update media playback service and permissions
ne0rrmatrix May 7, 2025
0feee28
Add tests for AndroidForegroundService default behavior
ne0rrmatrix May 7, 2025
f6407f6
Set service on by default
ne0rrmatrix May 8, 2025
63ad21b
Remove old intent filter that was added by accident
ne0rrmatrix May 8, 2025
1e60956
Rename AndroidForegroundServiceEnabled property
ne0rrmatrix May 8, 2025
5a3c0fe
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jun 3, 2025
904ae95
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jun 8, 2025
ea35514
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jun 25, 2025
cecaf32
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jul 3, 2025
178e8ae
Enable Android Foreground Service for MediaElement
ne0rrmatrix Jul 3, 2025
25b22c6
Merge branch 'MediaElementOptionService' of https://github.com/ne0rrm…
ne0rrmatrix Jul 3, 2025
1168302
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jul 3, 2025
838d9a5
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jul 3, 2025
7aba464
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jul 8, 2025
a83de86
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jul 30, 2025
ed62a22
Update src/CommunityToolkit.Maui.MediaElement/MediaElementOptions.sha…
ne0rrmatrix Aug 3, 2025
84b337c
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Aug 3, 2025
58e07a8
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Oct 10, 2025
cb63525
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Oct 16, 2025
54830f8
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Nov 16, 2025
74dfd56
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Nov 23, 2025
e205ac3
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Nov 24, 2025
8599d5c
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Dec 17, 2025
9b20b70
Refactor MediaElement Android Foreground Service config
ne0rrmatrix Dec 17, 2025
0588103
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Dec 22, 2025
ca6ec4a
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jan 8, 2026
7a4e51b
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jan 19, 2026
af24853
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jan 19, 2026
d00dcf7
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jan 20, 2026
747f95b
Merge branch 'main' into MediaElementOptionService
ne0rrmatrix Jan 20, 2026
4106c25
Switch to using using OptIn and move from options to builder to set s…
ne0rrmatrix Jan 20, 2026
045e95f
Merge branch 'MediaElementOptionService' of https://github.com/ne0rrm…
ne0rrmatrix Jan 20, 2026
eeeba09
Fix tests
ne0rrmatrix Jan 20, 2026
859eb74
Require enableForegroundService param for MediaElement init
ne0rrmatrix Jan 21, 2026
349607a
Remove Analyzer and associated tests
ne0rrmatrix Jan 21, 2026
a783e5b
Update src/CommunityToolkit.Maui.UnitTests/Views/MediaElement/MediaEl…
ne0rrmatrix Jan 21, 2026
268abca
Update comment to add href to documentation for MediaElement when set…
ne0rrmatrix Jan 21, 2026
f6dc22a
Update URL to not be region specific
ne0rrmatrix Jan 22, 2026
5b2e117
Update MediaElementOptions tests to reflect no default value for whet…
ne0rrmatrix Jan 22, 2026
28f912a
Copilot Suggestion
ne0rrmatrix Jan 22, 2026
9eb07b8
Copilot Sugestions and update test to remove setting default for Serv…
ne0rrmatrix Jan 22, 2026
2094ae8
Update src/CommunityToolkit.Maui.UnitTests/BaseTest.cs
ne0rrmatrix Jan 22, 2026
20c3af6
Copilot suggestions
ne0rrmatrix Jan 22, 2026
483a6d7
C# standards
ne0rrmatrix Jan 22, 2026
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
10 changes: 6 additions & 4 deletions samples/CommunityToolkit.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ public static MauiApp CreateMauiApp()
#endif
.UseMauiCommunityToolkitMarkup()
.UseMauiCommunityToolkitCamera()
.UseMauiCommunityToolkitMediaElement(static options =>
{
options.SetDefaultAndroidViewType(AndroidViewType.TextureView);
})
.UseMauiCommunityToolkitMediaElement(
enableForegroundService: true,
static options =>
{
options.SetDefaultAndroidViewType(AndroidViewType.TextureView);
})
.ConfigureMauiHandlers(static handlers =>
{
#if IOS || MACCATALYST
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:enableOnBackInvokedCallback="true" android:hardwareAccelerated="true"
android:supportsRtl="true">

<meta-data android:name="com.google.android.geo.API_KEY" android:value="PASTE-YOUR-API-KEY-HERE" />
</application>

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
android:supportsRtl="true">

<!-- Samsung -->
<uses-permission android:name="com.sec.android.provider.badge.permission.READ"/>
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE"/>
<!-- /Samsung -->
<meta-data android:name="com.google.android.geo.API_KEY" android:value="PASTE-YOUR-API-KEY-HERE" />

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<queries>
<intent>
<action android:name="android.intent.action.TTS_SERVICE"/>
</intent>
</queries>
<service android:name="communityToolkit.maui.media.services" android:stopWithTask="true" android:exported="false" android:enabled="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
</application>

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
<!-- Samsung -->
<uses-permission android:name="com.sec.android.provider.badge.permission.READ"/>
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE"/>
<!-- /Samsung -->

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<queries>
<intent>
<action android:name="android.intent.action.TTS_SERVICE"/>
</intent>
</queries>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.MediaElement.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
Expand Down Expand Up @@ -34,7 +34,7 @@ public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<Microsoft.Maui.Controls.Application>()
.UseMauiCommunityToolkitMediaElement()
.UseMauiCommunityToolkitMediaElement(enableForegroundService: true)
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
Expand Down Expand Up @@ -69,7 +69,7 @@ public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder ();
builder.UseMauiApp<Microsoft.Maui.Controls.Application> ()
.UseMauiCommunityToolkitMediaElement ()
.UseMauiCommunityToolkitMediaElement(enableForegroundService: true)
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
Expand Down Expand Up @@ -139,7 +139,7 @@ public static MauiApp CreateMauiApp()
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<Microsoft.Maui.Controls.Application>()
#if ANDROID || IOS
.UseMauiCommunityToolkitMediaElement()
.UseMauiCommunityToolkitMediaElement(enableForegroundService: true)
#endif
.ConfigureFonts(fonts =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,20 @@
/// Initializes the .NET MAUI Community Toolkit MediaElement Library
/// </summary>
/// <param name="builder"><see cref="MauiAppBuilder"/> generated by <see cref="MauiApp"/>.</param>
/// <param name="options"></param>
/// <param name="options"><see cref="MediaElementOptions"/>.</param>
/// <param name="enableForegroundService"> Enable Android Foreground Service for MediaElement.
/// When providing <c>true</c> the required permissions and service must be included in android manifest.
/// For more information, see the <see href="https://learn.microsoft.com/dotnet/communitytoolkit/maui/views/mediaelement?tabs=android">MediaElement Documentation.</see> for details.
/// </param>
/// <returns><see cref="MauiAppBuilder"/> initialized for <see cref="MediaElement"/>.</returns>
public static MauiAppBuilder UseMauiCommunityToolkitMediaElement(this MauiAppBuilder builder, Action<MediaElementOptions>? options = null)
public static MauiAppBuilder UseMauiCommunityToolkitMediaElement(this MauiAppBuilder builder, bool enableForegroundService, Action<MediaElementOptions>? options = null)
{
// Create MediaElementOptions and set the foreground service setting
var mediaElementOptions = new MediaElementOptions(builder);
mediaElementOptions.SetAndroidForegroundServiceEnabled(enableForegroundService);

// Update the default MediaElementOptions for MediaElement if Action is not null
options?.Invoke(new MediaElementOptions(builder));
options?.Invoke(mediaElementOptions);

// Perform Handler configuration
builder.ConfigureMauiHandlers(h =>
Expand All @@ -34,7 +42,7 @@
});

#if ANDROID
builder.Services.AddSingleton<Media.Services.MediaControlsService>();

Check warning on line 45 in src/CommunityToolkit.Maui.MediaElement/AppBuilderExtensions.shared.cs

View workflow job for this annotation

GitHub Actions / Build Library (macos-26)

This call site is reachable on: 'Android' 26.0 and later, 'iOS' 15.0 and later, 'maccatalyst' 15.0 and later, 'Tizen' 6.5 and later, 'Windows' 10.0.17763 and later. 'MediaControlsService' is only supported on: 'Android' 26.0 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 45 in src/CommunityToolkit.Maui.MediaElement/AppBuilderExtensions.shared.cs

View workflow job for this annotation

GitHub Actions / Build Library (macos-26)

This call site is reachable on: 'Android' 26.0 and later, 'iOS' 15.0 and later, 'maccatalyst' 15.0 and later, 'Tizen' 6.5 and later, 'Windows' 10.0.17763 and later. 'MediaControlsService' is only supported on: 'Android' 26.0 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
#endif

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected override MauiMediaElement CreatePlatformView()
VirtualView,
Dispatcher.GetForCurrentThread() ?? throw new InvalidOperationException($"{nameof(IDispatcher)} cannot be null"));

var (_, playerView) = MediaManager.CreatePlatformView(VirtualView.AndroidViewType);
var (_, playerView) = MediaManager.CreatePlatformView(VirtualView.AndroidViewType, VirtualView.IsAndroidForegroundServiceEnabled);
return new(Context, playerView);
}

Expand Down
5 changes: 5 additions & 0 deletions src/CommunityToolkit.Maui.MediaElement/MediaElement.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ internal event EventHandler StopRequested
/// </summary>
public AndroidViewType AndroidViewType { get; init; } = MediaElementOptions.DefaultAndroidViewType;

/// <summary>
/// Gets or sets a value indicating whether Android Foreground Service is enabled.
/// </summary>
public bool IsAndroidForegroundServiceEnabled { get; init; } = MediaElementOptions.IsAndroidForegroundServiceEnabled;

/// <summary>
/// Gets or sets the <see cref="Aspect"/> ratio used to display the video content.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,19 @@ internal MediaElementOptions(in MauiAppBuilder builder) : this()
/// </summary>
internal static AndroidViewType DefaultAndroidViewType { get; private set; } = AndroidViewType.SurfaceView;

/// <summary>
/// Set whether Android Foreground Service is enabled for MediaElement
/// </summary>
internal static bool IsAndroidForegroundServiceEnabled { get; private set; }

/// <summary>
/// Set Android View type for MediaElement as SurfaceView or TextureView on construction
/// </summary>
public void SetDefaultAndroidViewType(AndroidViewType androidViewType) => DefaultAndroidViewType = androidViewType;

/// <summary>
/// Enable Android Foreground Service for MediaElement
/// </summary>
/// <param name="enabled">True to enable foreground service, false to disable</param>
internal void SetAndroidForegroundServiceEnabled(bool enabled) => IsAndroidForegroundServiceEnabled = enabled;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
namespace CommunityToolkit.Maui.Media.Services;

[SupportedOSPlatform("Android26.0")]
[IntentFilter(["androidx.media3.session.MediaSessionService"])]
[Service(Exported = false, Enabled = true, Name = "communityToolkit.maui.media.services", ForegroundServiceType = ForegroundService.TypeMediaPlayback)]
sealed partial class MediaControlsService : Service
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
using AndroidX.Media3.UI;
using CommunityToolkit.Maui.Views;

[assembly: UsesPermission(Android.Manifest.Permission.ForegroundServiceMediaPlayback)]
[assembly: UsesPermission(Android.Manifest.Permission.ForegroundService)]
[assembly: UsesPermission(Android.Manifest.Permission.MediaContentControl)]
[assembly: UsesPermission(Android.Manifest.Permission.PostNotifications)]

namespace CommunityToolkit.Maui.Core.Views;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public partial class MediaManager : Java.Lang.Object, IPlayerListener

static readonly HttpClient client = new();
readonly SemaphoreSlim seekToSemaphoreSlim = new(1, 1);
bool isAndroidServiceEnabled = false;

double? previousSpeed;
float volumeBeforeMute = 1;
Expand Down Expand Up @@ -62,9 +63,8 @@ public void OnPlaybackParametersChanged(PlaybackParameters? playbackParameters)

public void UpdateNotifications()
{
if (connection?.Binder?.Service is null)
if (connection?.Binder?.Service is null || !isAndroidServiceEnabled)
{
System.Diagnostics.Trace.TraceInformation("Notification Service not running.");
return;
}

Expand Down Expand Up @@ -130,11 +130,11 @@ or PlaybackState.StateSkippingToQueueItem
/// <returns>The platform native counterpart of <see cref="MediaElement"/>.</returns>
/// <exception cref="NullReferenceException">Thrown when <see cref="Context"/> is <see langword="null"/> or when the platform view could not be created.</exception>
[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.isAndroidServiceEnabled = isAndroidServiceEnabled;
if (androidViewType is AndroidViewType.SurfaceView)
{
PlayerView = new PlayerView(MauiContext.Context)
Expand Down Expand Up @@ -353,7 +353,7 @@ protected virtual async partial ValueTask PlatformUpdateSource()
return;
}

if (connection is null)
if (connection is null && isAndroidServiceEnabled)
{
StartService();
}
Expand Down Expand Up @@ -381,10 +381,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 (isAndroidServiceEnabled)
{
UpdateNotifications();
}
}
}

Expand Down Expand Up @@ -631,9 +637,12 @@ static async ValueTask<long> GetByteCountFromStream(Stream stream, CancellationT
}
}

[MemberNotNull(nameof(connection))]
void StartService()
{
if (!isAndroidServiceEnabled)
{
return;
}
var intent = new Intent(Android.App.Application.Context, typeof(MediaControlsService));
connection = new BoundServiceConnection(this);
connection.MediaControlsServiceTaskRemoved += HandleMediaControlsServiceTaskRemoved;
Expand All @@ -644,6 +653,10 @@ void StartService()

void StopService(in BoundServiceConnection boundServiceConnection)
{
if (!isAndroidServiceEnabled)
{
return;
}
boundServiceConnection.MediaControlsServiceTaskRemoved -= HandleMediaControlsServiceTaskRemoved;

var serviceIntent = new Intent(Platform.AppContext, typeof(MediaControlsService));
Expand Down
2 changes: 1 addition & 1 deletion src/CommunityToolkit.Maui.UnitTests/BaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ protected virtual void Dispose(bool isDisposing)
// Restore default MediaElementOptions
var mediaElementOptions = new MediaElementOptions();
mediaElementOptions.SetDefaultAndroidViewType(AndroidViewType.SurfaceView);

mediaElementOptions.SetAndroidForegroundServiceEnabled(false);
isDisposed = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ void HandleShouldUseStatusBarBehaviorOnAndroidModalPageOptionCompleted(object? s
public void UseMauiCommunityToolkitMediaElement_ShouldUseSurfaceViewByDefault()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiCommunityToolkitMediaElement();
builder.UseMauiCommunityToolkitMediaElement(enableForegroundService: true);

MediaElementOptions.DefaultAndroidViewType.Should().Be(AndroidViewType.SurfaceView);
}
Expand All @@ -159,12 +159,28 @@ public void UseMauiCommunityToolkitMediaElement_ShouldSetDefaultAndroidViewType(
MediaElementOptions.DefaultAndroidViewType.Should().Be(AndroidViewType.SurfaceView);

var builder = MauiApp.CreateBuilder();
builder.UseMauiCommunityToolkitMediaElement(static options =>
builder.UseMauiCommunityToolkitMediaElement(enableForegroundService: true, static options =>
{
options.SetDefaultAndroidViewType(AndroidViewType.TextureView);
});

MediaElementOptions.DefaultAndroidViewType.Should().Be(AndroidViewType.TextureView);
}

[Fact]
public void UseMauiCommunityToolkitMediaElement_ShouldSetAndroidServiceByDefault()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiCommunityToolkitMediaElement(enableForegroundService: true);
MediaElementOptions.IsAndroidForegroundServiceEnabled.Should().Be(true);
}

[Fact]
public void UseMauiCommunityToolkitMediaElement_ServiceCanBeDisabled()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiCommunityToolkitMediaElement(enableForegroundService: false);
MediaElementOptions.IsAndroidForegroundServiceEnabled.Should().Be(false);
}
}
#pragma warning restore CA1416
Loading
Loading