diff --git a/Directory.build.props b/Directory.build.props index a6e5fa91..e725cbca 100644 --- a/Directory.build.props +++ b/Directory.build.props @@ -14,7 +14,7 @@ git $(AssemblyName) ($(TargetFramework)) en - 0.8.0 + 0.8.1 latest $(NoWarn);1591;1701;1702;1705;VSX1000;NU1603 diff --git a/MediaManager/IMediaManager.cs b/MediaManager/IMediaManager.cs index ee37d4e3..66535af0 100644 --- a/MediaManager/IMediaManager.cs +++ b/MediaManager/IMediaManager.cs @@ -57,7 +57,14 @@ public interface IMediaManager : IPlaybackManager, IDisposable /// /// /// - Task Play(string resourceName, Assembly assembly); + Task PlayFromAssembly(string resourceName, Assembly assembly = null); + + /// + /// Plays a native resource + /// + /// + /// + Task PlayFromResource(string resourceName); /// /// Plays a list of media items diff --git a/MediaManager/Media/IMediaExtractor.cs b/MediaManager/Media/IMediaExtractor.cs index 1996aa73..b877dc84 100644 --- a/MediaManager/Media/IMediaExtractor.cs +++ b/MediaManager/Media/IMediaExtractor.cs @@ -9,11 +9,16 @@ namespace MediaManager.Media public interface IMediaExtractor { IList RemotePrefixes { get; } + IList FilePrefixes { get; } + IList ResourcePrefixes { get; } + Task CreateMediaItem(string url); - Task CreateMediaItem(string resourceName, Assembly assembly); + Task CreateMediaItemFromAssembly(string resourceName, Assembly assembly = null); + + Task CreateMediaItemFromResource(string resourceName); Task CreateMediaItem(FileInfo file); diff --git a/MediaManager/Media/MediaExtractorBase.cs b/MediaManager/Media/MediaExtractorBase.cs index 74437add..f37eaeb8 100644 --- a/MediaManager/Media/MediaExtractorBase.cs +++ b/MediaManager/Media/MediaExtractorBase.cs @@ -11,13 +11,30 @@ public abstract class MediaExtractorBase : IMediaExtractor { protected Dictionary RequestHeaders => CrossMediaManager.Current.RequestHeaders; + public IList RemotePrefixes { get; } = new List() { + "http", + "udp", + "rtp" + }; + + public IList FilePrefixes { get; } = new List() { + "file", + "/", + "ms-appx", + "ms-appdata" + }; + + public IList ResourcePrefixes { get; } = new List() { + "android.resource" + }; + public virtual Task CreateMediaItem(string url) { var mediaItem = new MediaItem(url); return UpdateMediaItem(mediaItem); } - public virtual async Task CreateMediaItem(string resourceName, Assembly assembly) + public virtual async Task CreateMediaItemFromAssembly(string resourceName, Assembly assembly = null) { if (assembly == null) { @@ -37,21 +54,7 @@ public virtual async Task CreateMediaItem(string resourceName, Assem using (var stream = assembly.GetManifestResourceStream(resourcePaths.Single())) { - if (stream != null) - { - var tempDirectory = Path.Combine(Path.GetTempPath(), "EmbeddedResources"); - path = Path.Combine(tempDirectory, resourceName); - - if (!Directory.Exists(tempDirectory)) - { - Directory.CreateDirectory(tempDirectory); - } - - using (var tempFile = File.Create(path)) - { - await stream.CopyToAsync(tempFile).ConfigureAwait(false); - } - } + path = await CopyResourceStreamToFile(stream, "EmbeddedResources", resourceName).ConfigureAwait(false); } var mediaItem = new MediaItem(path); @@ -59,6 +62,15 @@ public virtual async Task CreateMediaItem(string resourceName, Assem return await UpdateMediaItem(mediaItem).ConfigureAwait(false); } + public virtual async Task CreateMediaItemFromResource(string resourceName) + { + var path = await GetResourcePath(resourceName).ConfigureAwait(false); + + var mediaItem = new MediaItem(path); + mediaItem.MediaLocation = MediaLocation.Resource; + return await UpdateMediaItem(mediaItem).ConfigureAwait(false); + } + public virtual Task CreateMediaItem(FileInfo file) { return CreateMediaItem(file.FullName); @@ -79,23 +91,32 @@ public virtual async Task UpdateMediaItem(IMediaItem mediaItem) return mediaItem; } - public abstract Task RetrieveMediaItemArt(IMediaItem mediaItem); + protected virtual async Task CopyResourceStreamToFile(Stream stream, string tempDirectoryName, string resourceName) + { + string path = null; - public abstract Task ExtractMetadata(IMediaItem mediaItem); + if (stream != null) + { + var tempDirectory = Path.Combine(Path.GetTempPath(), tempDirectoryName); + path = Path.Combine(tempDirectory, resourceName); - public IList RemotePrefixes { get; } = new List() { - "http", - "udp", - "rtp" - }; + if (!Directory.Exists(tempDirectory)) + { + Directory.CreateDirectory(tempDirectory); + } - public IList FilePrefixes { get; } = new List() { - "file", - "/", - "ms-appx", - "ms-appdata", - "android.resource" - }; + using (var tempFile = File.Create(path)) + { + await stream.CopyToAsync(tempFile).ConfigureAwait(false); + } + } + + return path; + } + + public abstract Task RetrieveMediaItemArt(IMediaItem mediaItem); + + public abstract Task ExtractMetadata(IMediaItem mediaItem); public virtual MediaLocation GetMediaLocation(IMediaItem mediaItem) { @@ -108,6 +129,14 @@ public virtual MediaLocation GetMediaLocation(IMediaItem mediaItem) } } + foreach (var item in ResourcePrefixes) + { + if (url.StartsWith(item)) + { + return MediaLocation.Resource; + } + } + foreach (var item in FilePrefixes) { if (url.StartsWith(item)) @@ -143,5 +172,7 @@ protected virtual bool TryFindAssembly(string resourceName, out Assembly assembl } public abstract Task GetVideoFrame(IMediaItem mediaItem, TimeSpan timeFromStart); + + protected abstract Task GetResourcePath(string resourceName); } } diff --git a/MediaManager/Media/MediaLocation.cs b/MediaManager/Media/MediaLocation.cs index 923b668b..129c7d21 100644 --- a/MediaManager/Media/MediaLocation.cs +++ b/MediaManager/Media/MediaLocation.cs @@ -5,6 +5,7 @@ public enum MediaLocation Unknown = 0, Remote, FileSystem, - Embedded + Embedded, + Resource } } diff --git a/MediaManager/MediaManagerBase.cs b/MediaManager/MediaManagerBase.cs index 4fa3e43c..8b9c39b7 100644 --- a/MediaManager/MediaManagerBase.cs +++ b/MediaManager/MediaManagerBase.cs @@ -145,9 +145,18 @@ public virtual async Task Play(string uri) return mediaItem; } - public virtual async Task Play(string resourceName, Assembly assembly) + public virtual async Task PlayFromAssembly(string resourceName, Assembly assembly = null) { - var mediaItem = await MediaExtractor.CreateMediaItem(resourceName, assembly).ConfigureAwait(false); + var mediaItem = await MediaExtractor.CreateMediaItemFromAssembly(resourceName, assembly).ConfigureAwait(false); + var mediaItemToPlay = await PrepareQueueForPlayback(mediaItem); + + await PlayAsCurrent(mediaItemToPlay); + return mediaItem; + } + + public virtual async Task PlayFromResource(string resourceName) + { + var mediaItem = await MediaExtractor.CreateMediaItemFromResource(resourceName).ConfigureAwait(false); var mediaItemToPlay = await PrepareQueueForPlayback(mediaItem); await PlayAsCurrent(mediaItemToPlay); diff --git a/MediaManager/Platforms/Android/Media/MediaExtractor.cs b/MediaManager/Platforms/Android/Media/MediaExtractor.cs index 1636314b..19df7ff6 100644 --- a/MediaManager/Platforms/Android/Media/MediaExtractor.cs +++ b/MediaManager/Platforms/Android/Media/MediaExtractor.cs @@ -180,7 +180,6 @@ public override async Task GetVideoFrame(IMediaItem mediaItem, TimeSpan { try { - var metaRetriever = new MediaMetadataRetriever(); switch (mediaItem.MediaLocation) @@ -204,5 +203,17 @@ public override async Task GetVideoFrame(IMediaItem mediaItem, TimeSpan } return null; } + + protected override async Task GetResourcePath(string resourceName) + { + string path = null; + + using (var stream = MediaManager.Context.Assets.Open(resourceName)) + { + path = await CopyResourceStreamToFile(stream, "AndroidResources", resourceName).ConfigureAwait(false); + } + + return path; + } } } diff --git a/MediaManager/Platforms/Apple/Media/AppleMediaExtractor.cs b/MediaManager/Platforms/Apple/Media/AppleMediaExtractor.cs index 091c971f..a1e90682 100644 --- a/MediaManager/Platforms/Apple/Media/AppleMediaExtractor.cs +++ b/MediaManager/Platforms/Apple/Media/AppleMediaExtractor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using AVFoundation; using CoreGraphics; @@ -80,5 +81,17 @@ public override Task GetVideoFrame(IMediaItem mediaItem, TimeSpan timeFr var cgImage = imageGenerator.CopyCGImageAtTime(new CMTime((long)timeFromStart.TotalMilliseconds, 1000000), out var actualTime, out var error); return Task.FromResult(cgImage as object); } + + protected override Task GetResourcePath(string resourceName) + { + string path = null; + + var filename = Path.GetFileNameWithoutExtension(resourceName); + var extension = Path.GetExtension(resourceName); + + path = NSBundle.MainBundle.PathForResource(filename, extension); + + return Task.FromResult(path); + } } } diff --git a/MediaManager/Platforms/Tizen/Media/MediaExtractor.cs b/MediaManager/Platforms/Tizen/Media/MediaExtractor.cs index 26498ec4..d52368ef 100644 --- a/MediaManager/Platforms/Tizen/Media/MediaExtractor.cs +++ b/MediaManager/Platforms/Tizen/Media/MediaExtractor.cs @@ -27,6 +27,11 @@ public override Task RetrieveMediaItemArt(IMediaItem mediaItem) return null; } + protected override Task GetResourcePath(string resourceName) + { + return null; + } + protected virtual void SetMetadata(IMediaItem mediaItem, MetadataExtractor extractor) { var metadata = extractor.GetMetadata(); diff --git a/MediaManager/Platforms/Uap/Media/MediaExtractor.cs b/MediaManager/Platforms/Uap/Media/MediaExtractor.cs index fe6810a3..8d617c2b 100644 --- a/MediaManager/Platforms/Uap/Media/MediaExtractor.cs +++ b/MediaManager/Platforms/Uap/Media/MediaExtractor.cs @@ -99,5 +99,10 @@ public async Task GetThumbnailAsync(StorageFile file, TimeSpan tim mediaComposition.Clips.Add(mediaClip); return await mediaComposition.GetThumbnailAsync(timeFromStart, 0, 0, VideoFramePrecision.NearestFrame); } + + protected override Task GetResourcePath(string resourceName) + { + return null; + } } } diff --git a/MediaManager/Platforms/Wpf/Media/MediaExtractor.cs b/MediaManager/Platforms/Wpf/Media/MediaExtractor.cs index 93e70681..c78b9338 100644 --- a/MediaManager/Platforms/Wpf/Media/MediaExtractor.cs +++ b/MediaManager/Platforms/Wpf/Media/MediaExtractor.cs @@ -20,5 +20,10 @@ public override Task RetrieveMediaItemArt(IMediaItem mediaItem) { return null; } + + protected override Task GetResourcePath(string resourceName) + { + return null; + } } } diff --git a/README.md b/README.md index b05270ac..44b729f3 100644 --- a/README.md +++ b/README.md @@ -90,14 +90,20 @@ await CrossMediaManager.Current.Play(Mp3UrlList); ### Other play possibilities ```csharp -Task Play(IMediaItem mediaItem); +Task Play(IMediaItem mediaItem); Task Play(string uri); -Task Play(IEnumerable items); -Task> Play(IEnumerable items); +Task Play(IEnumerable items); +Task Play(IEnumerable items); Task Play(FileInfo file); -Task> Play(DirectoryInfo directoryInfo); +Task Play(DirectoryInfo directoryInfo); +Task PlayFromAssembly(string resourceName, Assembly assembly = null); +Task PlayFromResource(string resourceName); ``` +* Playing from a `File` can be done for example by using the `File` and `Directory` api's. You download a file from the internet and save it somewhere using these .NET api's. +* When playing from `Assembly` you need to add a media file to a assembly and set the build action to `Embedded resource`. +* When playing from a `Resource` you should add your media file for example to the `Assets` or `raw` folder on Android, and the `Resources` folder on iOS. + ### Control the player ```csharp @@ -120,7 +126,12 @@ await CrossMediaManager.Current.PlayPrevious(); await CrossMediaManager.Current.PlayNext(); await CrossMediaManager.Current.PlayPreviousOrSeekToStart(); await CrossMediaManager.Current.PlayQueueItem(IMediaItem mediaItem); +await CrossMediaManager.Current.PlayQueueItem(int index); +``` +Extensions: + +```csharp void ToggleRepeat(); void ToggleShuffle(); ``` @@ -137,8 +148,29 @@ TimeSpan Buffered { get; } float Speed { get; set; } RepeatMode RepeatMode { get; set; } ShuffleMode ShuffleMode { get; set; } +bool ClearQueueOnPlay { get; set; } +bool AutoPlay { get; set; } +bool KeepScreenOn { get; set; } +``` + +Extensions: + +```csharp bool IsPlaying(); bool IsBuffering(); +bool IsPrepared(); +bool IsStopped(); +``` + +Properties available on CrossMediaManager.Current.MediaPlayer.* + +```csharp +IVideoView VideoView { get; set; } +bool AutoAttachVideoView { get; set; } +VideoAspectMode VideoAspect { get; set; } +bool ShowPlaybackControls { get; set; } +int VideoHeight { get; } +int VideoWidth { get; } ``` ### Hook into events @@ -173,6 +205,16 @@ mediaItem.MetadataUpdated += (sender, args) => { Alternatively you could also use the `PropertyChanged` event to see updates to the metadata. +You can also get a single frame from a video: + +```csharp +string url = "https://something.com/something.mov"; +var mediaItem = await CrossMediaManager.Current.MediaExtractor.CreateMediaItem(url); +var image = await CrossMediaManager.Current.MediaExtractor.GetVideoFrame(mediaItem, TimeSpan.FromSeconds(1)); +ImageSource imageSource = image.ToImageSource(); +FormsImage.Source = imageSource; +``` + ### Add Video Player to the UI **The video view will automatically be attached to the player. If you have multiple video views and you want to hook it up yourself do:** @@ -300,9 +342,10 @@ CrossMediaManager.Current.Reactive().* * This library will automatically request the following permissions: `AccessWifiState`, `AccessNetworkState`, `Internet`, `ForegroundService` and `WakeLock`. You do not need to add them to your AndroidManifest. * Your app must target Android SDK v28 or higher * This library uses ExoPlayer for video playback. This requires that you enable the following -* Dex tool to D8 `d8` -* R8 Linker `r8` +* Dex tool to D8: `d8` +* Optional enable R8 Linker to make code smaller: `r8` * Aapt2 build tools: `true` +* Disable multi-dex when using D8 and R8 with AAPT2. Your code should be small enough with those. **iOS:**