diff --git a/MediaManager.Abstractions/IVideoPlayer.cs b/MediaManager.Abstractions/IVideoPlayer.cs
index c5391533..c42bfe62 100644
--- a/MediaManager.Abstractions/IVideoPlayer.cs
+++ b/MediaManager.Abstractions/IVideoPlayer.cs
@@ -12,6 +12,11 @@ public interface IVideoPlayer : IPlaybackManager
///
IVideoSurface RenderSurface { get; set; }
+ ///
+ /// True when RenderSurface has been initialized and ready for rendering
+ ///
+ bool IsReadyRendering { get; }
+
///
/// The aspect mode of the video
///
diff --git a/MediaManager.Abstractions/IVideoSurface.cs b/MediaManager.Abstractions/IVideoSurface.cs
index 3d5f322a..ffb2e92f 100644
--- a/MediaManager.Abstractions/IVideoSurface.cs
+++ b/MediaManager.Abstractions/IVideoSurface.cs
@@ -5,5 +5,6 @@
///
public interface IVideoSurface
{
+ bool IsDisposed { get; }
}
}
diff --git a/MediaManager.Abstractions/Implementations/MediaManagerBase.cs b/MediaManager.Abstractions/Implementations/MediaManagerBase.cs
index 5b939d46..24772253 100644
--- a/MediaManager.Abstractions/Implementations/MediaManagerBase.cs
+++ b/MediaManager.Abstractions/Implementations/MediaManagerBase.cs
@@ -165,7 +165,7 @@ public async Task Play(IMediaFile mediaFile = null)
if (!MediaQueue.Contains(mediaFile))
MediaQueue.Add(mediaFile);
- MediaQueue.SetTrackAsCurrent(mediaFile);
+ MediaQueue.SetTrackAsCurrent(mediaFile);
await RaiseMediaFileFailedEventOnException(async () =>
{
@@ -385,9 +385,38 @@ private void RemoveEventHandlers()
_currentPlaybackManager.StatusChanged -= OnStatusChanged;
}
- public virtual void Dispose()
+ #region IDisposable
+ // Flag: Has Dispose already been called?
+ bool disposed = false;
+
+ // Public implementation of Dispose pattern callable by consumers.
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ // Protected implementation of Dispose pattern.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ return;
+
+ if (disposing)
+ {
+ // Free any other managed objects here.
+ RemoveEventHandlers();
+ }
+
+ // Free any unmanaged objects here.
+ //
+ disposed = true;
+ }
+
+ ~MediaManagerBase()
{
- RemoveEventHandlers();
+ Dispose(false);
}
+ #endregion
}
}
diff --git a/MediaManager.Android/Audio/AudioPlayerBase.cs b/MediaManager.Android/Audio/AudioPlayerBase.cs
index 8e926902..c25a7eff 100644
--- a/MediaManager.Android/Audio/AudioPlayerBase.cs
+++ b/MediaManager.Android/Audio/AudioPlayerBase.cs
@@ -10,7 +10,6 @@
using Plugin.MediaManager.Abstractions;
using Plugin.MediaManager.Abstractions.Enums;
using Plugin.MediaManager.Abstractions.EventArguments;
-using Plugin.MediaManager.Abstractions.Implementations;
using Plugin.MediaManager.MediaSession;
namespace Plugin.MediaManager
diff --git a/MediaManager.Android/Video/VideoPlayerImplementation.cs b/MediaManager.Android/Video/VideoPlayerImplementation.cs
index d58b59ad..868a8474 100644
--- a/MediaManager.Android/Video/VideoPlayerImplementation.cs
+++ b/MediaManager.Android/Video/VideoPlayerImplementation.cs
@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
-using Android.App;
-using Android.Content.Res;
using Android.Media;
using Android.OS;
using Android.Runtime;
@@ -13,7 +10,6 @@
using Plugin.MediaManager.Abstractions;
using Plugin.MediaManager.Abstractions.Enums;
using Plugin.MediaManager.Abstractions.EventArguments;
-using Plugin.MediaManager.Abstractions.Implementations;
namespace Plugin.MediaManager
{
@@ -30,9 +26,9 @@ public VideoPlayerImplementation()
StatusChanged += (sender, args) => OnPlayingHandler(args);
}
- private bool isPlayerReady = false;
+ private bool isPlayerReady = false;
- private IScheduledExecutorService _executorService = Executors.NewSingleThreadScheduledExecutor();
+ private IScheduledExecutorService _executorService = Executors.NewSingleThreadScheduledExecutor();
private IScheduledFuture _scheduledFuture;
private void OnPlayingHandler(StatusChangedEventArgs args)
@@ -45,7 +41,7 @@ private void OnPlayingHandler(StatusChangedEventArgs args)
if (args.Status == MediaPlayerStatus.Stopped || args.Status == MediaPlayerStatus.Failed || args.Status == MediaPlayerStatus.Paused)
CancelPlayingHandler();
}
-
+
private void CancelPlayingHandler()
{
_scheduledFuture?.Cancel(false);
@@ -56,14 +52,17 @@ private void StartPlayingHandler()
var handler = new Handler();
var runnable = new Runnable(() => { handler.Post(OnPlaying); });
if (!_executorService.IsShutdown)
- {
- _scheduledFuture = _executorService.ScheduleAtFixedRate(runnable, 100, 1000, TimeUnit.Milliseconds);
+ {
+ _scheduledFuture = _executorService.ScheduleAtFixedRate(runnable, 100, 1000, TimeUnit.Milliseconds);
}
}
private void OnPlaying()
{
- var progress = (Position.TotalSeconds / Duration.TotalSeconds);
+ if (!IsReadyRendering)
+ CancelPlayingHandler(); //RenderSurface is no longer valid => Cancel the periodic firing
+
+ var progress = (Position.TotalSeconds / Duration.TotalSeconds);
var position = Position;
var duration = Duration;
@@ -73,6 +72,13 @@ private void OnPlaying()
duration.TotalSeconds >= 0 ? duration : TimeSpan.Zero));
}
+ ///
+ /// True when RenderSurface has been initialized and ready for rendering
+ ///
+ public bool IsReadyRendering => RenderSurface != null && !RenderSurface.IsDisposed;
+
+ VideoView VideoViewCanvas => RenderSurface as VideoView;
+
private IVideoSurface _renderSurface;
public IVideoSurface RenderSurface {
get {
@@ -82,8 +88,14 @@ public IVideoSurface RenderSurface {
if (!(value is VideoSurface))
throw new ArgumentException("Not a valid video surface");
- var canvas = (VideoSurface)value;
+ if (_renderSurface == value)
+ return;
+
+ var canvas = (VideoSurface)value;
_renderSurface = canvas;
+
+ //New canvas object => need initialization
+ isPlayerReady = false;
}
}
@@ -97,9 +109,7 @@ public VideoAspectMode AspectMode {
//TODO: Wrap videoplayer to respect aspectmode
_aspectMode = value;
}
- }
-
- VideoView VideoViewCanvas => RenderSurface as VideoView;
+ }
public IMediaFile CurrentFile { get; set; }
private Android.Net.Uri currentUri { get; set; }
@@ -150,13 +160,14 @@ protected virtual void OnMediaFileFailed(MediaFileFailedEventArgs e)
{
MediaFileFailed?.Invoke(this, e);
}
+
+ public TimeSpan Buffered => IsReadyRendering == false ? TimeSpan.Zero : TimeSpan.FromSeconds(VideoViewCanvas.BufferPercentage);
- public TimeSpan Buffered => VideoViewCanvas == null ? TimeSpan.Zero : TimeSpan.FromSeconds(VideoViewCanvas.BufferPercentage);
+ public TimeSpan Duration => IsReadyRendering == false ? TimeSpan.Zero : TimeSpan.FromSeconds(VideoViewCanvas.Duration);
+
+ public TimeSpan Position => IsReadyRendering == false ? TimeSpan.Zero : TimeSpan.FromSeconds(VideoViewCanvas.CurrentPosition);
- public TimeSpan Duration => VideoViewCanvas == null ? TimeSpan.Zero : TimeSpan.FromSeconds(VideoViewCanvas.Duration);
-
- public TimeSpan Position => VideoViewCanvas == null ? TimeSpan.Zero : TimeSpan.FromSeconds(VideoViewCanvas.CurrentPosition);
- private int lastPosition = 0;
+ private int lastPosition = 0;
private MediaPlayerStatus _status = MediaPlayerStatus.Stopped;
public MediaPlayerStatus Status
@@ -187,17 +198,18 @@ public void Init()
mediaController.SetAnchorView(VideoViewCanvas);
VideoViewCanvas.SetMediaController(mediaController);
}
-
+
VideoViewCanvas.SetOnCompletionListener(this);
VideoViewCanvas.SetOnErrorListener(this);
VideoViewCanvas.SetOnPreparedListener(this);
VideoViewCanvas.SetOnInfoListener(this);
}
-
+
public async Task Play(IMediaFile mediaFile = null)
- {
- if (VideoViewCanvas == null)
- throw new System.Exception("No canvas set for video to play in");
+ {
+ if (!IsReadyRendering)
+ //Android ViewRenderer might not initialize Control yet
+ return;
if (isPlayerReady == false)
{
@@ -256,12 +268,16 @@ public async Task Stop()
public void OnCompletion(MediaPlayer mp)
{
+ Console.WriteLine($"OnCompletion");
+
OnMediaFinished(new MediaFinishedEventArgs(CurrentFile));
}
public bool OnError(MediaPlayer mp, MediaError what, int extra)
{
- Stop().Wait();
+ Console.WriteLine($"OnError: {what}");
+
+ Stop().Wait();
Status = MediaPlayerStatus.Failed;
OnMediaFailed(new MediaFailedEventArgs(what.ToString(), new System.Exception()));
return true;
@@ -269,7 +285,9 @@ public bool OnError(MediaPlayer mp, MediaError what, int extra)
public void OnPrepared(MediaPlayer mp)
{
- if(Status == MediaPlayerStatus.Buffering)
+ Console.WriteLine($"OnPrepared: {Status}");
+
+ if (Status == MediaPlayerStatus.Buffering)
VideoViewCanvas.Start();
Status = MediaPlayerStatus.Playing;
@@ -277,7 +295,9 @@ public void OnPrepared(MediaPlayer mp)
public bool OnInfo(MediaPlayer mp, [GeneratedEnum] MediaInfo what, int extra)
{
- switch (what)
+ Console.WriteLine($"OnInfo: {what}");
+
+ switch (what)
{
case MediaInfo.BadInterleaving:
break;
@@ -302,6 +322,6 @@ public bool OnInfo(MediaPlayer mp, [GeneratedEnum] MediaInfo what, int extra)
}
return true;
- }
+ }
}
}
diff --git a/MediaManager.Android/Video/VideoSurface.cs b/MediaManager.Android/Video/VideoSurface.cs
index c4692f9a..6cf479be 100644
--- a/MediaManager.Android/Video/VideoSurface.cs
+++ b/MediaManager.Android/Video/VideoSurface.cs
@@ -34,5 +34,16 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
}
+
+ #region IDisposable
+ public bool IsDisposed { get; private set; }
+
+ protected override void Dispose(bool disposing)
+ {
+ IsDisposed = true;
+
+ base.Dispose(disposing);
+ }
+ #endregion
}
}
diff --git a/MediaManager.Forms/MediaManager.Forms.Android/VideoViewRenderer.cs b/MediaManager.Forms/MediaManager.Forms.Android/VideoViewRenderer.cs
index 3cac0c18..dc25e460 100644
--- a/MediaManager.Forms/MediaManager.Forms.Android/VideoViewRenderer.cs
+++ b/MediaManager.Forms/MediaManager.Forms.Android/VideoViewRenderer.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Plugin.MediaManager.Forms;
using Plugin.MediaManager.Forms.Android;
using Xamarin.Forms;
@@ -11,7 +11,7 @@ public class VideoViewRenderer : ViewRenderer
{
private VideoSurface _videoSurface;
- public static async void Init()
+ public static void Init()
{
var temp = DateTime.Now;
}
@@ -24,7 +24,7 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
_videoSurface = new VideoSurface(Context);
SetNativeControl(_videoSurface);
CrossMediaManager.Current.VideoPlayer.RenderSurface = _videoSurface;
- }
+ }
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
diff --git a/MediaManager.Forms/MediaManager.Forms/VideoView.cs b/MediaManager.Forms/MediaManager.Forms/VideoView.cs
index 9e1c673a..a0a28a64 100644
--- a/MediaManager.Forms/MediaManager.Forms/VideoView.cs
+++ b/MediaManager.Forms/MediaManager.Forms/VideoView.cs
@@ -42,15 +42,16 @@ private static void OnAspectModeChanged(BindableObject bindable, object oldvalue
{
CrossMediaManager.Current.VideoPlayer.AspectMode = ((VideoAspectMode) newvalue);
}
-
+
private static void OnSourceChanged(BindableObject bindable, object oldvalue, object newvalue)
- {
+ {
var video = new MediaFile
{
Url = (string)newvalue,
Type = MediaFileType.Video
};
-
+
+ //Auto play by adding video to the queue and then play
CrossMediaManager.Current.Play(video);
}
}
diff --git a/MediaManager.MacOS/VideoSurface.cs b/MediaManager.MacOS/VideoSurface.cs
index e487d6d4..c5655ee9 100644
--- a/MediaManager.MacOS/VideoSurface.cs
+++ b/MediaManager.MacOS/VideoSurface.cs
@@ -5,5 +5,15 @@ namespace Plugin.MediaManager
{
public class VideoSurface : NSView, IVideoSurface
{
+ #region IDisposable
+ public bool IsDisposed { get; private set; }
+
+ protected override void Dispose(bool disposing)
+ {
+ IsDisposed = true;
+
+ base.Dispose(disposing);
+ }
+ #endregion
}
}
diff --git a/MediaManager.UWP/MediaManagerImplementation.cs b/MediaManager.UWP/MediaManagerImplementation.cs
index 21ec1650..d31f6351 100644
--- a/MediaManager.UWP/MediaManagerImplementation.cs
+++ b/MediaManager.UWP/MediaManagerImplementation.cs
@@ -39,10 +39,25 @@ public override IVideoPlayer VideoPlayer
public override IVolumeManager VolumeManager { get; set; } = new VolumeManagerImplementation();
- public override void Dispose()
+ #region IDisposable
+ bool disposed = false;
+ protected override void Dispose(bool disposing)
{
- base.Dispose();
- _mediaButtonPlaybackController.UnsubscribeFromNotifications();
+ if (disposed)
+ return;
+
+ if (disposing)
+ {
+ // Free any other managed objects here.
+ _mediaButtonPlaybackController.UnsubscribeFromNotifications();
+ }
+
+ base.Dispose(disposing);
+
+ // Free any unmanaged objects here.
+ //
+ disposed = true;
}
+ #endregion
}
}
\ No newline at end of file
diff --git a/MediaManager.UWP/VideoPlayerImplementation.cs b/MediaManager.UWP/VideoPlayerImplementation.cs
index 919b8ca7..799acb2c 100644
--- a/MediaManager.UWP/VideoPlayerImplementation.cs
+++ b/MediaManager.UWP/VideoPlayerImplementation.cs
@@ -203,6 +203,11 @@ public Task Stop()
return Task.CompletedTask;
}
+ ///
+ /// True when RenderSurface has been initialized and ready for rendering
+ ///
+ public bool IsReadyRendering => RenderSurface != null && !RenderSurface.IsDisposed;
+
public IVideoSurface RenderSurface
{
get { return _renderSurface; }
diff --git a/MediaManager.UWP/VideoSurface.cs b/MediaManager.UWP/VideoSurface.cs
index 39b8ba6a..d4cfc266 100644
--- a/MediaManager.UWP/VideoSurface.cs
+++ b/MediaManager.UWP/VideoSurface.cs
@@ -1,10 +1,44 @@
-using Windows.UI.Xaml.Controls;
+using System;
+using Windows.UI.Xaml.Controls;
using Plugin.MediaManager.Abstractions;
namespace Plugin.MediaManager
{
- public class VideoSurface : Canvas, IVideoSurface
+ public class VideoSurface : Canvas, IVideoSurface, IDisposable
{
-
+ #region IDisposable
+ public bool IsDisposed => disposed;
+
+ // Flag: Has Dispose already been called?
+ bool disposed = false;
+
+ // Public implementation of Dispose pattern callable by consumers.
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ // Protected implementation of Dispose pattern.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ return;
+
+ if (disposing)
+ {
+ // Free any other managed objects here.
+ }
+
+ // Free any unmanaged objects here.
+ //
+ disposed = true;
+ }
+
+ ~VideoSurface()
+ {
+ Dispose(false);
+ }
+ #endregion
}
}
diff --git a/MediaManager.iOS/VideoPlayerImplementation.cs b/MediaManager.iOS/VideoPlayerImplementation.cs
index 013ee2bf..e22b6ffc 100644
--- a/MediaManager.iOS/VideoPlayerImplementation.cs
+++ b/MediaManager.iOS/VideoPlayerImplementation.cs
@@ -10,7 +10,6 @@
using Plugin.MediaManager.Abstractions;
using Plugin.MediaManager.Abstractions.Enums;
using Plugin.MediaManager.Abstractions.EventArguments;
-using Plugin.MediaManager.Abstractions.Implementations;
namespace Plugin.MediaManager
{
@@ -319,6 +318,11 @@ private void ObserveLoadedTimeRanges()
}
}
+ ///
+ /// True when RenderSurface has been initialized and ready for rendering
+ ///
+ public bool IsReadyRendering => RenderSurface != null && !RenderSurface.IsDisposed;
+
private IVideoSurface _renderSurface;
public IVideoSurface RenderSurface
{
diff --git a/MediaManager.iOS/VideoSurface.cs b/MediaManager.iOS/VideoSurface.cs
index 6dfc792a..357e45d5 100644
--- a/MediaManager.iOS/VideoSurface.cs
+++ b/MediaManager.iOS/VideoSurface.cs
@@ -1,7 +1,4 @@
-using System;
-using AVFoundation;
-using CoreGraphics;
-using Foundation;
+using AVFoundation;
using Plugin.MediaManager.Abstractions;
using UIKit;
@@ -22,5 +19,16 @@ public override void LayoutSubviews()
avPlayerLayer.Frame = Bounds;
}
}
+
+ #region IDisposable
+ public bool IsDisposed { get; private set; }
+
+ protected override void Dispose(bool disposing)
+ {
+ IsDisposed = true;
+
+ base.Dispose(disposing);
+ }
+ #endregion
}
}
diff --git a/MediaManager.tvOS/VideoSurface.cs b/MediaManager.tvOS/VideoSurface.cs
index 347e86b7..46a3bf63 100644
--- a/MediaManager.tvOS/VideoSurface.cs
+++ b/MediaManager.tvOS/VideoSurface.cs
@@ -5,5 +5,15 @@ namespace Plugin.MediaManager
{
public class VideoSurface : UIView, IVideoSurface
{
+ #region IDisposable
+ public bool IsDisposed { get; private set; }
+
+ protected override void Dispose(bool disposing)
+ {
+ IsDisposed = true;
+
+ base.Dispose(disposing);
+ }
+ #endregion
}
}
diff --git a/Samples/Forms/MediaForms/App.xaml.cs b/Samples/Forms/MediaForms/App.xaml.cs
index 8a994093..f8a31ca0 100644
--- a/Samples/Forms/MediaForms/App.xaml.cs
+++ b/Samples/Forms/MediaForms/App.xaml.cs
@@ -10,7 +10,9 @@ public App()
// Make sure it doesn't get stripped away by the linker
var workaround = typeof(VideoView);
InitializeComponent();
- MainPage = new MediaFormsPage();
+ //MainPage = new MediaFormsPage();
+
+ MainPage = new NavigationPage(new HomePage());
}
protected override void OnStart()
diff --git a/Samples/Forms/MediaForms/HomePage.xaml b/Samples/Forms/MediaForms/HomePage.xaml
new file mode 100644
index 00000000..62ccf979
--- /dev/null
+++ b/Samples/Forms/MediaForms/HomePage.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Forms/MediaForms/HomePage.xaml.cs b/Samples/Forms/MediaForms/HomePage.xaml.cs
new file mode 100644
index 00000000..dd5e4a38
--- /dev/null
+++ b/Samples/Forms/MediaForms/HomePage.xaml.cs
@@ -0,0 +1,20 @@
+using System;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace MediaForms
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class HomePage : ContentPage
+ {
+ public HomePage()
+ {
+ InitializeComponent();
+ }
+
+ private void MainBtn_OnClicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new MediaFormsPage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/Forms/MediaForms/MediaForms.csproj b/Samples/Forms/MediaForms/MediaForms.csproj
index 8c3bde96..acdba621 100644
--- a/Samples/Forms/MediaForms/MediaForms.csproj
+++ b/Samples/Forms/MediaForms/MediaForms.csproj
@@ -38,6 +38,9 @@
App.xaml
+
+ HomePage.xaml
+
MediaFormsPage.xaml
@@ -66,6 +69,12 @@
+
+
+ MSBuild:UpdateDesignTimeXaml
+ Designer
+
+
diff --git a/Samples/Forms/MediaForms/MediaFormsPage.xaml b/Samples/Forms/MediaForms/MediaFormsPage.xaml
index d8f9b455..b744874f 100644
--- a/Samples/Forms/MediaForms/MediaFormsPage.xaml
+++ b/Samples/Forms/MediaForms/MediaFormsPage.xaml
@@ -1,4 +1,4 @@
-
+
-
-
+
+
+
+
+
+
diff --git a/Samples/Forms/MediaForms/MediaFormsPage.xaml.cs b/Samples/Forms/MediaForms/MediaFormsPage.xaml.cs
index 3fc71d6d..c0f32daf 100644
--- a/Samples/Forms/MediaForms/MediaFormsPage.xaml.cs
+++ b/Samples/Forms/MediaForms/MediaFormsPage.xaml.cs
@@ -11,6 +11,7 @@ public partial class MediaFormsPage : ContentPage
public MediaFormsPage()
{
InitializeComponent();
+
CrossMediaManager.Current.PlayingChanged += (sender, e) =>
{
Device.BeginInvokeOnMainThread(() =>
@@ -18,11 +19,16 @@ public MediaFormsPage()
ProgressBar.Progress = e.Progress;
Duration.Text = "" + e.Duration.TotalSeconds + " seconds";
});
- };
+ };
}
- void PlayClicked(object sender, System.EventArgs e)
+ protected override void OnAppearing()
{
+ videoView.Source = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
+ }
+
+ void PlayClicked(object sender, System.EventArgs e)
+ {
PlaybackController.Play();
}