Skip to content

Commit

Permalink
Merge pull request #568 from orzech85/native_resources_support
Browse files Browse the repository at this point in the history
Add support for native resources (iOS, Android)
  • Loading branch information
martijn00 authored Aug 4, 2019
2 parents cfa343f + c097a64 commit fc0ea87
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Directory.build.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<RepositoryType>git</RepositoryType>
<Product>$(AssemblyName) ($(TargetFramework))</Product>
<NeutralLanguage>en</NeutralLanguage>
<Version>0.8.0</Version>
<Version>0.8.1</Version>

<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);1591;1701;1702;1705;VSX1000;NU1603</NoWarn>
Expand Down
9 changes: 8 additions & 1 deletion MediaManager/IMediaManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ public interface IMediaManager : IPlaybackManager, IDisposable
/// <param name="resourceName"></param>
/// <param name="assembly"></param>
/// <returns></returns>
Task<IMediaItem> Play(string resourceName, Assembly assembly);
Task<IMediaItem> PlayFromAssembly(string resourceName, Assembly assembly = null);

/// <summary>
/// Plays a native resource
/// </summary>
/// <param name="resourceName"></param>
/// <returns></returns>
Task<IMediaItem> PlayFromResource(string resourceName);

/// <summary>
/// Plays a list of media items
Expand Down
7 changes: 6 additions & 1 deletion MediaManager/Media/IMediaExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ namespace MediaManager.Media
public interface IMediaExtractor
{
IList<string> RemotePrefixes { get; }

IList<string> FilePrefixes { get; }

IList<string> ResourcePrefixes { get; }

Task<IMediaItem> CreateMediaItem(string url);

Task<IMediaItem> CreateMediaItem(string resourceName, Assembly assembly);
Task<IMediaItem> CreateMediaItemFromAssembly(string resourceName, Assembly assembly = null);

Task<IMediaItem> CreateMediaItemFromResource(string resourceName);

Task<IMediaItem> CreateMediaItem(FileInfo file);

Expand Down
91 changes: 61 additions & 30 deletions MediaManager/Media/MediaExtractorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,30 @@ public abstract class MediaExtractorBase : IMediaExtractor
{
protected Dictionary<string, string> RequestHeaders => CrossMediaManager.Current.RequestHeaders;

public IList<string> RemotePrefixes { get; } = new List<string>() {
"http",
"udp",
"rtp"
};

public IList<string> FilePrefixes { get; } = new List<string>() {
"file",
"/",
"ms-appx",
"ms-appdata"
};

public IList<string> ResourcePrefixes { get; } = new List<string>() {
"android.resource"
};

public virtual Task<IMediaItem> CreateMediaItem(string url)
{
var mediaItem = new MediaItem(url);
return UpdateMediaItem(mediaItem);
}

public virtual async Task<IMediaItem> CreateMediaItem(string resourceName, Assembly assembly)
public virtual async Task<IMediaItem> CreateMediaItemFromAssembly(string resourceName, Assembly assembly = null)
{
if (assembly == null)
{
Expand All @@ -37,28 +54,23 @@ public virtual async Task<IMediaItem> 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);
mediaItem.MediaLocation = MediaLocation.Embedded;
return await UpdateMediaItem(mediaItem).ConfigureAwait(false);
}

public virtual async Task<IMediaItem> 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<IMediaItem> CreateMediaItem(FileInfo file)
{
return CreateMediaItem(file.FullName);
Expand All @@ -79,23 +91,32 @@ public virtual async Task<IMediaItem> UpdateMediaItem(IMediaItem mediaItem)
return mediaItem;
}

public abstract Task<object> RetrieveMediaItemArt(IMediaItem mediaItem);
protected virtual async Task<string> CopyResourceStreamToFile(Stream stream, string tempDirectoryName, string resourceName)
{
string path = null;

public abstract Task<IMediaItem> ExtractMetadata(IMediaItem mediaItem);
if (stream != null)
{
var tempDirectory = Path.Combine(Path.GetTempPath(), tempDirectoryName);
path = Path.Combine(tempDirectory, resourceName);

public IList<string> RemotePrefixes { get; } = new List<string>() {
"http",
"udp",
"rtp"
};
if (!Directory.Exists(tempDirectory))
{
Directory.CreateDirectory(tempDirectory);
}

public IList<string> FilePrefixes { get; } = new List<string>() {
"file",
"/",
"ms-appx",
"ms-appdata",
"android.resource"
};
using (var tempFile = File.Create(path))
{
await stream.CopyToAsync(tempFile).ConfigureAwait(false);
}
}

return path;
}

public abstract Task<object> RetrieveMediaItemArt(IMediaItem mediaItem);

public abstract Task<IMediaItem> ExtractMetadata(IMediaItem mediaItem);

public virtual MediaLocation GetMediaLocation(IMediaItem mediaItem)
{
Expand All @@ -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))
Expand Down Expand Up @@ -143,5 +172,7 @@ protected virtual bool TryFindAssembly(string resourceName, out Assembly assembl
}

public abstract Task<object> GetVideoFrame(IMediaItem mediaItem, TimeSpan timeFromStart);

protected abstract Task<string> GetResourcePath(string resourceName);
}
}
3 changes: 2 additions & 1 deletion MediaManager/Media/MediaLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public enum MediaLocation
Unknown = 0,
Remote,
FileSystem,
Embedded
Embedded,
Resource
}
}
13 changes: 11 additions & 2 deletions MediaManager/MediaManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,18 @@ public virtual async Task<IMediaItem> Play(string uri)
return mediaItem;
}

public virtual async Task<IMediaItem> Play(string resourceName, Assembly assembly)
public virtual async Task<IMediaItem> 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<IMediaItem> PlayFromResource(string resourceName)
{
var mediaItem = await MediaExtractor.CreateMediaItemFromResource(resourceName).ConfigureAwait(false);
var mediaItemToPlay = await PrepareQueueForPlayback(mediaItem);

await PlayAsCurrent(mediaItemToPlay);
Expand Down
13 changes: 12 additions & 1 deletion MediaManager/Platforms/Android/Media/MediaExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ public override async Task<object> GetVideoFrame(IMediaItem mediaItem, TimeSpan
{
try
{

var metaRetriever = new MediaMetadataRetriever();

switch (mediaItem.MediaLocation)
Expand All @@ -204,5 +203,17 @@ public override async Task<object> GetVideoFrame(IMediaItem mediaItem, TimeSpan
}
return null;
}

protected override async Task<string> GetResourcePath(string resourceName)
{
string path = null;

using (var stream = MediaManager.Context.Assets.Open(resourceName))
{
path = await CopyResourceStreamToFile(stream, "AndroidResources", resourceName).ConfigureAwait(false);
}

return path;
}
}
}
13 changes: 13 additions & 0 deletions MediaManager/Platforms/Apple/Media/AppleMediaExtractor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using AVFoundation;
using CoreGraphics;
Expand Down Expand Up @@ -80,5 +81,17 @@ public override Task<object> 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<string> 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);
}
}
}
5 changes: 5 additions & 0 deletions MediaManager/Platforms/Tizen/Media/MediaExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public override Task<object> RetrieveMediaItemArt(IMediaItem mediaItem)
return null;
}

protected override Task<string> GetResourcePath(string resourceName)
{
return null;
}

protected virtual void SetMetadata(IMediaItem mediaItem, MetadataExtractor extractor)
{
var metadata = extractor.GetMetadata();
Expand Down
5 changes: 5 additions & 0 deletions MediaManager/Platforms/Uap/Media/MediaExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,10 @@ public async Task<IInputStream> GetThumbnailAsync(StorageFile file, TimeSpan tim
mediaComposition.Clips.Add(mediaClip);
return await mediaComposition.GetThumbnailAsync(timeFromStart, 0, 0, VideoFramePrecision.NearestFrame);
}

protected override Task<string> GetResourcePath(string resourceName)
{
return null;
}
}
}
5 changes: 5 additions & 0 deletions MediaManager/Platforms/Wpf/Media/MediaExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,10 @@ public override Task<object> RetrieveMediaItemArt(IMediaItem mediaItem)
{
return null;
}

protected override Task<string> GetResourcePath(string resourceName)
{
return null;
}
}
}
55 changes: 49 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,20 @@ await CrossMediaManager.Current.Play(Mp3UrlList);
### Other play possibilities

```csharp
Task Play(IMediaItem mediaItem);
Task<IMediaItem> Play(IMediaItem mediaItem);
Task<IMediaItem> Play(string uri);
Task Play(IEnumerable<IMediaItem> items);
Task<IEnumerable<IMediaItem>> Play(IEnumerable<string> items);
Task<IMediaItem> Play(IEnumerable<IMediaItem> items);
Task<IMediaItem> Play(IEnumerable<string> items);
Task<IMediaItem> Play(FileInfo file);
Task<IEnumerable<IMediaItem>> Play(DirectoryInfo directoryInfo);
Task<IMediaItem> Play(DirectoryInfo directoryInfo);
Task<IMediaItem> PlayFromAssembly(string resourceName, Assembly assembly = null);
Task<IMediaItem> 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
Expand All @@ -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();
```
Expand All @@ -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
Expand Down Expand Up @@ -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:**
Expand Down Expand Up @@ -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 `<AndroidDexTool>d8</AndroidDexTool>`
* R8 Linker `<AndroidLinkTool>r8</AndroidLinkTool>`
* Dex tool to D8: `<AndroidDexTool>d8</AndroidDexTool>`
* Optional enable R8 Linker to make code smaller: `<AndroidLinkTool>r8</AndroidLinkTool>`
* Aapt2 build tools: `<AndroidUseAapt2>true</AndroidUseAapt2>`
* Disable multi-dex when using D8 and R8 with AAPT2. Your code should be small enough with those.

**iOS:**

Expand Down

0 comments on commit fc0ea87

Please sign in to comment.