[Core,iOS,Droid,UWP,Tizen] Remove MediaElement (#12007)
* [Core,UWP,iOS,Android] Remove MediaElement * [Tizen] Remove MediaElement * [WPF] Remove reference to MediaElement * [Tests] Remove MediaSourceTests * [Core] Remove MediaSource * [Tizen] Remove extra files related with MediaElement * Update ExperimentalFlags.cs * [Controls] Removing References to MedialElement strings * [Controls] Remove reference to MediaElement flag
This commit is contained in:
Родитель
c05e04d1cb
Коммит
6b816f7148
|
@ -166,9 +166,6 @@ namespace Xamarin.Forms.Platform
|
|||
#endif
|
||||
internal class _MasterDetailPageRenderer { }
|
||||
|
||||
[RenderWith (typeof(MediaElementRenderer))]
|
||||
internal class _MediaElementRenderer { }
|
||||
|
||||
[RenderWith(typeof(RefreshViewRenderer))]
|
||||
internal class _RefreshViewRenderer { }
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace Xamarin.Forms.ControlGallery.Android
|
|||
|
||||
#if TEST_EXPERIMENTAL_RENDERERS
|
||||
#else
|
||||
Forms.SetFlags("UseLegacyRenderers", "SwipeView_Experimental", "MediaElement_Experimental");
|
||||
Forms.SetFlags("UseLegacyRenderers", "SwipeView_Experimental");
|
||||
#endif
|
||||
Forms.Init(this, bundle);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Xamarin.Forms.ControlGallery.Tizen
|
|||
{
|
||||
var app = new MainApplication();
|
||||
FormsMaps.Init("HERE", "write-your-API-key-here");
|
||||
Forms.SetFlags("CollectionView_Experimental", "Shell_Experimental", "MediaElement_Experimental");
|
||||
Forms.SetFlags("CollectionView_Experimental", "Shell_Experimental");
|
||||
Forms.Init(app);
|
||||
FormsMaterial.Init();
|
||||
app.Run(args);
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Xamarin.Forms.ControlGallery.WPF
|
|||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Forms.SetFlags("CarouselView_Experimental", "MediaElement_Experimental", "RadioButton_Experimental");
|
||||
Forms.SetFlags("CarouselView_Experimental", "RadioButton_Experimental");
|
||||
Xamarin.Forms.Forms.Init();
|
||||
FormsMaps.Init("");
|
||||
LoadApplication(new Controls.App());
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace Xamarin.Forms.ControlGallery.WindowsUniversal
|
|||
|
||||
rootFrame.NavigationFailed += OnNavigationFailed;
|
||||
|
||||
Forms.SetFlags("Shell_UWP_Experimental", "SwipeView_Experimental", "MediaElement_Experimental");
|
||||
Forms.SetFlags("Shell_UWP_Experimental", "SwipeView_Experimental");
|
||||
Forms.Init (e);
|
||||
//FormsMaps.Init (Controls.App.Config["UWPMapsAuthKey"]);
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ namespace Xamarin.Forms.ControlGallery.iOS
|
|||
App.IOSVersion = int.Parse(versionPart[0]);
|
||||
|
||||
Xamarin.Calabash.Start();
|
||||
Forms.SetFlags("SwipeView_Experimental", "MediaElement_Experimental");
|
||||
Forms.SetFlags("SwipeView_Experimental");
|
||||
Forms.Init();
|
||||
FormsMaps.Init();
|
||||
FormsMaterial.Init();
|
||||
|
|
|
@ -415,7 +415,6 @@ namespace Xamarin.Forms.Controls
|
|||
new GalleryPageFactory(() => new ListViewDemoPage(), "ListView Demo Gallery - Legacy"),
|
||||
new GalleryPageFactory(() => new MapGallery(), "Map Gallery - Legacy"),
|
||||
new GalleryPageFactory(() => new MapWithItemsSourceGallery(), "Map With ItemsSource Gallery - Legacy"),
|
||||
new GalleryPageFactory(() => new MediaElementDemoPage(), "MediaElement"),
|
||||
new GalleryPageFactory(() => new MapElementsGallery(), "Map Elements Gallery - Legacy"),
|
||||
new GalleryPageFactory(() => new MinimumSizeGallery(), "MinimumSize Gallery - Legacy"),
|
||||
new GalleryPageFactory(() => new MultiGallery(), "Multi Gallery - Legacy"),
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Xamarin.Forms.Controls
|
||||
{
|
||||
internal class MediaElementDemoPage : ContentPage
|
||||
{
|
||||
MediaElement element;
|
||||
Label positionLabel;
|
||||
Label consoleLabel;
|
||||
|
||||
public MediaElementDemoPage()
|
||||
{
|
||||
element = new MediaElement();
|
||||
element.HorizontalOptions = new LayoutOptions(LayoutAlignment.Fill,true);
|
||||
element.VerticalOptions = new LayoutOptions(LayoutAlignment.Fill,true);
|
||||
element.MinimumWidthRequest = 320;
|
||||
element.MinimumHeightRequest = 240;
|
||||
element.AutoPlay = false;
|
||||
element.Aspect = Aspect.AspectFill;
|
||||
element.ShowsPlaybackControls = true;
|
||||
element.BackgroundColor = Color.Red;
|
||||
element.MediaEnded += Element_MediaEnded;
|
||||
element.MediaFailed += Element_MediaFailed;
|
||||
element.MediaOpened += Element_MediaOpened;
|
||||
consoleLabel = new Label();
|
||||
|
||||
var infoStack = new StackLayout { Orientation = StackOrientation.Horizontal };
|
||||
|
||||
var stateLabel = new Label();
|
||||
stateLabel.SetBinding(Label.TextProperty, new Binding("CurrentState", BindingMode.OneWay, null, null, "s:{0}", element));
|
||||
var bufferingLabel = new Label();
|
||||
bufferingLabel.SetBinding(Label.TextProperty, new Binding("BufferingProgress", BindingMode.OneWay, null, null, "b:{0:f2}", element));
|
||||
var heightLabel = new Label();
|
||||
heightLabel.SetBinding(Label.TextProperty, new Binding("VideoHeight", BindingMode.OneWay, null, null, "h:{0}", element));
|
||||
var widthLabel = new Label();
|
||||
widthLabel.SetBinding(Label.TextProperty, new Binding("VideoWidth", BindingMode.OneWay, null, null, "w:{0}", element));
|
||||
var durationLabel = new Label();
|
||||
durationLabel.SetBinding(Label.TextProperty, new Binding("Duration", BindingMode.OneWay, null, null, "d:{0:g}", element));
|
||||
var volumeLabel = new Label();
|
||||
volumeLabel.SetBinding(Label.TextProperty, new Binding("Volume", BindingMode.OneWay, null, null, "v:{0}", element));
|
||||
infoStack.Children.Add(stateLabel);
|
||||
infoStack.Children.Add(bufferingLabel);
|
||||
infoStack.Children.Add(heightLabel);
|
||||
infoStack.Children.Add(widthLabel);
|
||||
infoStack.Children.Add(durationLabel);
|
||||
infoStack.Children.Add(volumeLabel);
|
||||
|
||||
positionLabel = new Label();
|
||||
positionLabel.TextColor = Color.Black;
|
||||
//positionLabel.SetBinding(Label.TextProperty, new Binding("Position", BindingMode.OneWay, null, null, "{0:g}", element));
|
||||
|
||||
var playButton = new Button();
|
||||
playButton.Text = "\u25b6\uFE0F";
|
||||
playButton.FontSize = 48;
|
||||
playButton.HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, true);
|
||||
playButton.Clicked += PlayButton_Clicked;
|
||||
|
||||
var pauseButton = new Button();
|
||||
pauseButton.Text = "\u23f8\uFE0F";
|
||||
pauseButton.FontSize = 48;
|
||||
pauseButton.HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, true);
|
||||
pauseButton.Clicked += PauseButton_Clicked;
|
||||
|
||||
var stopButton = new Button();
|
||||
stopButton.Text = "\u23f9\uFE0F";
|
||||
stopButton.FontSize = 48;
|
||||
stopButton.HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, true);
|
||||
stopButton.Clicked += StopButton_Clicked;
|
||||
|
||||
var showControlsSwitch = new Switch();
|
||||
showControlsSwitch.SetBinding(Switch.IsToggledProperty, new Binding("ShowsPlaybackControls", BindingMode.TwoWay, source: element));
|
||||
|
||||
|
||||
var mediaControlStack = new StackLayout();
|
||||
mediaControlStack.Orientation = StackOrientation.Horizontal;
|
||||
mediaControlStack.HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, false);
|
||||
mediaControlStack.Children.Add(playButton);
|
||||
mediaControlStack.Children.Add(pauseButton);
|
||||
mediaControlStack.Children.Add(stopButton);
|
||||
mediaControlStack.Children.Add(showControlsSwitch);
|
||||
|
||||
var aspectFitButton = new Button() { Text = "Aspect Fit" };
|
||||
aspectFitButton.Clicked += (s, e) => { element.Aspect = Aspect.AspectFit; };
|
||||
var aspectFillButton = new Button() { Text = "Aspect Fill" };
|
||||
aspectFillButton.Clicked += (s, e) => { element.Aspect = Aspect.AspectFill; };
|
||||
var fillButton = new Button() { Text = "Fill" };
|
||||
fillButton.Clicked += (s, e) => { element.Aspect = Aspect.Fill; };
|
||||
|
||||
var aspectStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, false) };
|
||||
aspectStack.Children.Add(aspectFitButton);
|
||||
aspectStack.Children.Add(aspectFillButton);
|
||||
aspectStack.Children.Add(fillButton);
|
||||
|
||||
var volumeSlider = new Slider()
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 1
|
||||
};
|
||||
volumeSlider.Value = 0.1;
|
||||
|
||||
volumeSlider.ValueChanged += VolumeSlider_ValueChanged;
|
||||
|
||||
var stack = new StackLayout();
|
||||
stack.Padding = new Thickness(10);
|
||||
stack.Spacing = 10;
|
||||
stack.HorizontalOptions = new LayoutOptions(LayoutAlignment.Fill, false);
|
||||
stack.VerticalOptions = new LayoutOptions(LayoutAlignment.Fill, false);
|
||||
stack.Children.Add(element);
|
||||
stack.Children.Add(infoStack);
|
||||
stack.Children.Add(positionLabel);
|
||||
stack.Children.Add(mediaControlStack);
|
||||
stack.Children.Add(aspectStack);
|
||||
stack.Children.Add(consoleLabel);
|
||||
stack.Children.Add(new Label() { Text = "Volume:" });
|
||||
stack.Children.Add(volumeSlider);
|
||||
element.Volume = 0.1;
|
||||
Content = stack;
|
||||
}
|
||||
|
||||
private void VolumeSlider_ValueChanged(object sender, ValueChangedEventArgs e)
|
||||
{
|
||||
element.Volume = (sender as Slider).Value;
|
||||
}
|
||||
|
||||
void Element_MediaOpened(object sender, EventArgs e)
|
||||
{
|
||||
consoleLabel.Text += "Media opened" + Environment.NewLine;
|
||||
}
|
||||
|
||||
void Element_MediaFailed(object sender, EventArgs e)
|
||||
{
|
||||
consoleLabel.Text += "Media failed" + Environment.NewLine;
|
||||
}
|
||||
|
||||
void Element_MediaEnded(object sender, EventArgs e)
|
||||
{
|
||||
consoleLabel.Text += "Media ended" + Environment.NewLine;
|
||||
}
|
||||
|
||||
void PlayButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
element.Play();
|
||||
}
|
||||
|
||||
void PauseButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
element.Pause();
|
||||
}
|
||||
|
||||
void StopButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
element.Stop();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
if(Device.RuntimePlatform == Device.WPF)
|
||||
{
|
||||
// workaround for lack of https support on WPF
|
||||
element.Source = new Uri("http://sec.ch9.ms/ch9/5d93/a1eab4bf-3288-4faf-81c4-294402a85d93/XamarinShow_mid.mp4");
|
||||
}
|
||||
else
|
||||
{
|
||||
element.Source = new Uri("https://sec.ch9.ms/ch9/5d93/a1eab4bf-3288-4faf-81c4-294402a85d93/XamarinShow_mid.mp4");
|
||||
}
|
||||
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(100), ()=>{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
positionLabel.Text = element.Position.ToString("mm\\:ss\\.ff");
|
||||
});
|
||||
|
||||
return polling;
|
||||
});
|
||||
}
|
||||
|
||||
bool polling = true;
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
polling = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,7 +48,6 @@ namespace Xamarin.Forms.Markup.UnitTests
|
|||
{ typeof(InputView), na },
|
||||
{ typeof(ItemsLayout), na },
|
||||
{ typeof(LinearItemsLayout), na },
|
||||
{ typeof(MediaSource), na },
|
||||
{ typeof(Menu), na },
|
||||
{ typeof(MultiTrigger), na },
|
||||
{ typeof(NavigableElement), na },
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using NUnit.Framework;
|
||||
using Xamarin.Forms.PlatformConfiguration;
|
||||
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
|
||||
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
|
||||
|
||||
namespace Xamarin.Forms.Core.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class MediaElementTests : BaseTestFixture
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSource()
|
||||
{
|
||||
var mediaElement = new MediaElement();
|
||||
|
||||
Assert.IsNull(mediaElement.Source);
|
||||
|
||||
bool signaled = false;
|
||||
mediaElement.PropertyChanged += (sender, e) => {
|
||||
if (e.PropertyName == "Source")
|
||||
signaled = true;
|
||||
};
|
||||
|
||||
var source = MediaSource.FromFile("Video.mp4");
|
||||
mediaElement.Source = source;
|
||||
|
||||
Assert.AreEqual(source, mediaElement.Source);
|
||||
Assert.True(signaled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSourceDoubleSet()
|
||||
{
|
||||
var mediaElement = new MediaElement { Source = MediaSource.FromFile("Video.mp4") };
|
||||
|
||||
bool signaled = false;
|
||||
mediaElement.PropertyChanged += (sender, e) => {
|
||||
if (e.PropertyName == "Source")
|
||||
signaled = true;
|
||||
};
|
||||
|
||||
mediaElement.Source = mediaElement.Source;
|
||||
|
||||
Assert.False(signaled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFileMediaSourceChanged()
|
||||
{
|
||||
var source = (FileMediaSource)MediaSource.FromFile("Video.mp4");
|
||||
|
||||
bool signaled = false;
|
||||
source.SourceChanged += (sender, e) => {
|
||||
signaled = true;
|
||||
};
|
||||
|
||||
source.File = "Other.mp4";
|
||||
Assert.AreEqual("Other.mp4", source.File);
|
||||
|
||||
Assert.True(signaled);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestSourceRoundTrip()
|
||||
{
|
||||
var uri = new Uri("https://sec.ch9.ms/ch9/5d93/a1eab4bf-3288-4faf-81c4-294402a85d93/XamarinShow_mid.mp4");
|
||||
var media = new MediaElement();
|
||||
Assert.Null(media.Source);
|
||||
media.Source = uri;
|
||||
Assert.NotNull(media.Source);
|
||||
Assert.IsInstanceOf(typeof(UriMediaSource), media.Source, "Not expected mediasource type");
|
||||
Assert.AreEqual(uri, ((UriMediaSource)media.Source).Uri);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
|
||||
namespace Xamarin.Forms.Core.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class MediaSourceTests : BaseTestFixture
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
Device.PlatformServices = new MockPlatformServices();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConstructors()
|
||||
{
|
||||
var filesource = new FileMediaSource { File = "File.mp4" };
|
||||
Assert.AreEqual("File.mp4", filesource.File);
|
||||
|
||||
var urisource = new UriMediaSource { Uri = new Uri("http://xamarin.com/media.mp4") };
|
||||
Assert.AreEqual("http://xamarin.com/media.mp4", urisource.Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHelpers()
|
||||
{
|
||||
var mediasource = MediaSource.FromFile("File.mp4");
|
||||
Assert.That(mediasource, Is.TypeOf<FileMediaSource>());
|
||||
Assert.AreEqual("File.mp4", ((FileMediaSource)mediasource).File);
|
||||
|
||||
var urisource = MediaSource.FromUri(new Uri("http://xamarin.com/media.mp4"));
|
||||
Assert.That(urisource, Is.TypeOf<UriMediaSource>());
|
||||
Assert.AreEqual("http://xamarin.com/media.mp4", ((UriMediaSource)(urisource)).Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImplicitFileConversion()
|
||||
{
|
||||
var mediaElement = new MediaElement { Source = "File.mp4" };
|
||||
Assert.IsTrue(mediaElement.Source != null);
|
||||
Assert.That(mediaElement.Source, Is.InstanceOf<FileMediaSource>());
|
||||
Assert.AreEqual("File.mp4", ((FileMediaSource)(mediaElement.Source)).File);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImplicitStringConversionWhenNull()
|
||||
{
|
||||
string s = null;
|
||||
var sut = (MediaSource)s;
|
||||
Assert.That(sut, Is.InstanceOf<FileMediaSource>());
|
||||
Assert.IsNull(((FileMediaSource)sut).File);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImplicitUriConversion()
|
||||
{
|
||||
var mediaElement = new MediaElement { Source = new Uri("http://xamarin.com/media.mp4") };
|
||||
Assert.IsTrue(mediaElement.Source != null);
|
||||
Assert.That(mediaElement.Source, Is.InstanceOf<UriMediaSource>());
|
||||
Assert.AreEqual("http://xamarin.com/media.mp4", ((UriMediaSource)(mediaElement.Source)).Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImplicitStringUriConversion()
|
||||
{
|
||||
var mediaElement = new MediaElement { Source = "http://xamarin.com/media.mp4" };
|
||||
Assert.IsTrue(mediaElement.Source != null);
|
||||
Assert.That(mediaElement.Source, Is.InstanceOf<UriMediaSource>());
|
||||
Assert.AreEqual("http://xamarin.com/media.mp4", ((UriMediaSource)(mediaElement.Source)).Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImplicitUriConversionWhenNull()
|
||||
{
|
||||
Uri u = null;
|
||||
var sut = (MediaSource)u;
|
||||
Assert.IsNull(sut);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetStringValue()
|
||||
{
|
||||
var mediaElement = new MediaElement();
|
||||
mediaElement.SetValue(MediaElement.SourceProperty, "media.mp4");
|
||||
Assert.IsNotNull(mediaElement.Source);
|
||||
Assert.That(mediaElement.Source, Is.InstanceOf<FileMediaSource>());
|
||||
Assert.AreEqual("media.mp4", ((FileMediaSource)(mediaElement.Source)).File);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextBindToStringValue()
|
||||
{
|
||||
var mediaElement = new MediaElement();
|
||||
mediaElement.SetBinding(MediaElement.SourceProperty, ".");
|
||||
Assert.IsNull(mediaElement.Source);
|
||||
mediaElement.BindingContext = "media.mp4";
|
||||
Assert.IsNotNull(mediaElement.Source);
|
||||
Assert.That(mediaElement.Source, Is.InstanceOf<FileMediaSource>());
|
||||
Assert.AreEqual("media.mp4", ((FileMediaSource)(mediaElement.Source)).File);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextBindToStringUriValue()
|
||||
{
|
||||
var mediaElement = new MediaElement();
|
||||
mediaElement.SetBinding(MediaElement.SourceProperty, ".");
|
||||
Assert.IsNull(mediaElement.Source);
|
||||
mediaElement.BindingContext = "http://xamarin.com/media.mp4";
|
||||
Assert.IsNotNull(mediaElement.Source);
|
||||
Assert.That(mediaElement.Source, Is.InstanceOf<UriMediaSource>());
|
||||
Assert.AreEqual("http://xamarin.com/media.mp4", ((UriMediaSource)(mediaElement.Source)).Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextBindToUriValue()
|
||||
{
|
||||
var mediaElement = new MediaElement();
|
||||
mediaElement.SetBinding(MediaElement.SourceProperty, ".");
|
||||
Assert.IsNull(mediaElement.Source);
|
||||
mediaElement.BindingContext = new Uri("http://xamarin.com/media.mp4");
|
||||
Assert.IsNotNull(mediaElement.Source);
|
||||
Assert.That(mediaElement.Source, Is.InstanceOf<UriMediaSource>());
|
||||
Assert.AreEqual("http://xamarin.com/media.mp4", ((UriMediaSource)(mediaElement.Source)).Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
class MockMediaSource : MediaSource
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBindingContextPropagation()
|
||||
{
|
||||
var context = new object();
|
||||
var mediaElement = new MediaElement();
|
||||
mediaElement.BindingContext = context;
|
||||
var source = new MockMediaSource();
|
||||
mediaElement.Source = source;
|
||||
Assert.AreSame(context, source.BindingContext);
|
||||
|
||||
mediaElement = new MediaElement();
|
||||
source = new MockMediaSource();
|
||||
mediaElement.Source = source;
|
||||
mediaElement.BindingContext = context;
|
||||
Assert.AreSame(context, source.BindingContext);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ImplicitCastOnAbsolutePathsShouldCreateAFileMediaSource()
|
||||
{
|
||||
var path = "/private/var/mobile/Containers/Data/Application/B1E5AB19-F815-4B4A-AB97-BD4571D53743/Documents/temp/video.mp4";
|
||||
var mediaElement = new MediaElement { Source = path };
|
||||
Assert.That(mediaElement.Source, Is.TypeOf<FileMediaSource>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ImplicitCastOnWindowsAbsolutePathsShouldCreateAFileMediaSource()
|
||||
{
|
||||
var path = "C:\\Users\\Username\\Videos\\video.mp4";
|
||||
var mediaElement = new MediaElement { Source = path };
|
||||
Assert.That(mediaElement.Source, Is.TypeOf<FileMediaSource>());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -119,8 +119,6 @@
|
|||
<Compile Include="ImageButtonUnitTest.cs" />
|
||||
<Compile Include="ItemsViewTests.cs" />
|
||||
<Compile Include="LayoutChildIntoBoundingRegionTests.cs" />
|
||||
<Compile Include="MediaElementTests.cs" />
|
||||
<Compile Include="MediaSourceTests.cs" />
|
||||
<Compile Include="MenuUnitTests.cs" />
|
||||
<Compile Include="PreviewerReflectionTests.cs" />
|
||||
<Compile Include="RegionTests.cs" />
|
||||
|
|
|
@ -12,7 +12,6 @@ namespace Xamarin.Forms
|
|||
internal const string ShellUWPExperimental = "Shell_UWP_Experimental";
|
||||
internal const string CarouselViewExperimental = "CarouselView_Experimental";
|
||||
internal const string SwipeViewExperimental = "SwipeView_Experimental";
|
||||
internal const string MediaElementExperimental = "MediaElement_Experimental";
|
||||
internal const string MarkupExperimental = "Markup_Experimental";
|
||||
internal const string RadioButtonExperimental = "RadioButton_Experimental";
|
||||
internal const string ShapesExperimental = "Shapes_Experimental";
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
[TypeConverter(typeof(FileMediaSourceConverter))]
|
||||
public sealed class FileMediaSource : MediaSource
|
||||
{
|
||||
public static readonly BindableProperty FileProperty = BindableProperty.Create(nameof(File), typeof(string), typeof(FileMediaSource), default(string));
|
||||
|
||||
public string File
|
||||
{
|
||||
get { return (string)GetValue(FileProperty); }
|
||||
set { SetValue(FileProperty, value); }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"File: {File}";
|
||||
}
|
||||
|
||||
public static implicit operator FileMediaSource(string file)
|
||||
{
|
||||
return (FileMediaSource)FromFile(file);
|
||||
}
|
||||
|
||||
public static implicit operator string(FileMediaSource file)
|
||||
{
|
||||
return file != null ? file.File : null;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(string propertyName = null)
|
||||
{
|
||||
if (propertyName == FileProperty.PropertyName)
|
||||
OnSourceChanged();
|
||||
base.OnPropertyChanged(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
[Xaml.TypeConversion(typeof(FileMediaSource))]
|
||||
public sealed class FileMediaSourceConverter : TypeConverter
|
||||
{
|
||||
public override object ConvertFromInvariantString(string value)
|
||||
{
|
||||
if (value != null)
|
||||
return (FileMediaSource)MediaSource.FromFile(value);
|
||||
|
||||
throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(FileMediaSource)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ namespace Xamarin.Forms.Markup
|
|||
{ "Xamarin.Forms.Entry", Entry.TextProperty },
|
||||
{ "Xamarin.Forms.EntryCell", EntryCell.TextProperty },
|
||||
{ "Xamarin.Forms.FileImageSource", FileImageSource.FileProperty },
|
||||
{ "Xamarin.Forms.FileMediaSource", FileMediaSource.FileProperty },
|
||||
{ "Xamarin.Forms.HtmlWebViewSource", HtmlWebViewSource.HtmlProperty },
|
||||
{ "Xamarin.Forms.Image", Image.SourceProperty },
|
||||
{ "Xamarin.Forms.ImageButton", ImageButton.CommandProperty },
|
||||
|
@ -33,7 +32,6 @@ namespace Xamarin.Forms.Markup
|
|||
{ "Xamarin.Forms.Label", Label.TextProperty },
|
||||
{ "Xamarin.Forms.ListView", ListView.ItemsSourceProperty },
|
||||
{ "Xamarin.Forms.MasterDetailPage", Page.TitleProperty },
|
||||
{ "Xamarin.Forms.MediaElement", MediaElement.SourceProperty },
|
||||
{ "Xamarin.Forms.MenuItem", MenuItem.CommandProperty },
|
||||
{ "Xamarin.Forms.MultiPage", Page.TitleProperty },
|
||||
{ "Xamarin.Forms.NavigationPage", Page.TitleProperty },
|
||||
|
@ -62,7 +60,6 @@ namespace Xamarin.Forms.Markup
|
|||
{ "Xamarin.Forms.TimePicker", TimePicker.TimeProperty },
|
||||
{ "Xamarin.Forms.ToolbarItem", ToolbarItem.CommandProperty },
|
||||
{ "Xamarin.Forms.UriImageSource", UriImageSource.UriProperty },
|
||||
{ "Xamarin.Forms.UriMediaSource", UriMediaSource.UriProperty },
|
||||
{ "Xamarin.Forms.UrlWebViewSource", UrlWebViewSource.UrlProperty },
|
||||
{ "Xamarin.Forms.WebView", WebView.SourceProperty }
|
||||
};
|
||||
|
|
|
@ -1,309 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.Platform;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
[RenderWith(typeof(_MediaElementRenderer))]
|
||||
public sealed class MediaElement : View, IMediaElementController
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static void VerifyMediaElementFlagEnabled(
|
||||
string constructorHint = null,
|
||||
[CallerMemberName] string memberName = "")
|
||||
{
|
||||
ExperimentalFlags.VerifyFlagEnabled(nameof(MediaElement), ExperimentalFlags.MediaElementExperimental, memberName: memberName);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty AspectProperty =
|
||||
BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(MediaElement), Aspect.AspectFit);
|
||||
|
||||
public static readonly BindableProperty AutoPlayProperty =
|
||||
BindableProperty.Create(nameof(AutoPlay), typeof(bool), typeof(MediaElement), true);
|
||||
|
||||
public static readonly BindableProperty BufferingProgressProperty =
|
||||
BindableProperty.Create(nameof(BufferingProgress), typeof(double), typeof(MediaElement), 0.0);
|
||||
|
||||
public static readonly BindableProperty CurrentStateProperty =
|
||||
BindableProperty.Create(nameof(CurrentState), typeof(MediaElementState), typeof(MediaElement), MediaElementState.Closed);
|
||||
|
||||
public static readonly BindableProperty DurationProperty =
|
||||
BindableProperty.Create(nameof(Duration), typeof(TimeSpan?), typeof(MediaElement), null);
|
||||
|
||||
public static readonly BindableProperty IsLoopingProperty =
|
||||
BindableProperty.Create(nameof(IsLooping), typeof(bool), typeof(MediaElement), false);
|
||||
|
||||
public static readonly BindableProperty KeepScreenOnProperty =
|
||||
BindableProperty.Create(nameof(KeepScreenOn), typeof(bool), typeof(MediaElement), false);
|
||||
|
||||
public static readonly BindableProperty PositionProperty =
|
||||
BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(MediaElement), TimeSpan.Zero);
|
||||
|
||||
public static readonly BindableProperty ShowsPlaybackControlsProperty =
|
||||
BindableProperty.Create(nameof(ShowsPlaybackControls), typeof(bool), typeof(MediaElement), false);
|
||||
|
||||
public static readonly BindableProperty SourceProperty =
|
||||
BindableProperty.Create(nameof(Source), typeof(MediaSource), typeof(MediaElement),
|
||||
propertyChanging: OnSourcePropertyChanging, propertyChanged: OnSourcePropertyChanged);
|
||||
|
||||
public static readonly BindableProperty VideoHeightProperty =
|
||||
BindableProperty.Create(nameof(VideoHeight), typeof(int), typeof(MediaElement));
|
||||
|
||||
public static readonly BindableProperty VideoWidthProperty =
|
||||
BindableProperty.Create(nameof(VideoWidth), typeof(int), typeof(MediaElement));
|
||||
|
||||
public static readonly BindableProperty VolumeProperty =
|
||||
BindableProperty.Create(nameof(Volume), typeof(double), typeof(MediaElement), 1.0, BindingMode.TwoWay, new BindableProperty.ValidateValueDelegate(ValidateVolume));
|
||||
|
||||
private static bool ValidateVolume(BindableObject o, object newValue)
|
||||
{
|
||||
double d = (double)newValue;
|
||||
|
||||
return d >= 0.0 && d <= 1.0;
|
||||
}
|
||||
|
||||
public Aspect Aspect
|
||||
{
|
||||
get => (Aspect)GetValue(AspectProperty);
|
||||
set => SetValue(AspectProperty, value);
|
||||
}
|
||||
|
||||
public bool AutoPlay
|
||||
{
|
||||
get { return (bool)GetValue(AutoPlayProperty); }
|
||||
set { SetValue(AutoPlayProperty, value); }
|
||||
}
|
||||
|
||||
public double BufferingProgress
|
||||
{
|
||||
get { return (double)GetValue(BufferingProgressProperty); }
|
||||
}
|
||||
|
||||
public bool CanSeek
|
||||
{
|
||||
get { return Source != null && Duration.HasValue; }
|
||||
}
|
||||
|
||||
public MediaElementState CurrentState
|
||||
{
|
||||
get { return (MediaElementState)GetValue(CurrentStateProperty); }
|
||||
}
|
||||
|
||||
public TimeSpan? Duration
|
||||
{
|
||||
get { return (TimeSpan?)GetValue(DurationProperty); }
|
||||
}
|
||||
|
||||
public bool IsLooping
|
||||
{
|
||||
get { return (bool)GetValue(IsLoopingProperty); }
|
||||
set { SetValue(IsLoopingProperty, value); }
|
||||
}
|
||||
|
||||
public bool KeepScreenOn
|
||||
{
|
||||
get { return (bool)GetValue(KeepScreenOnProperty); }
|
||||
set { SetValue(KeepScreenOnProperty, value); }
|
||||
}
|
||||
|
||||
public bool ShowsPlaybackControls
|
||||
{
|
||||
get { return (bool)GetValue(ShowsPlaybackControlsProperty); }
|
||||
set { SetValue(ShowsPlaybackControlsProperty, value); }
|
||||
}
|
||||
|
||||
public TimeSpan Position
|
||||
{
|
||||
get
|
||||
{
|
||||
PositionRequested?.Invoke(this, EventArgs.Empty);
|
||||
return (TimeSpan)GetValue(PositionProperty);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SeekRequested?.Invoke(this, new SeekRequested(value));
|
||||
}
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(MediaSourceConverter))]
|
||||
public MediaSource Source
|
||||
{
|
||||
get { return (MediaSource)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public int VideoHeight
|
||||
{
|
||||
get { return (int)GetValue(VideoHeightProperty); }
|
||||
}
|
||||
|
||||
public int VideoWidth
|
||||
{
|
||||
get { return (int)GetValue(VideoWidthProperty); }
|
||||
}
|
||||
|
||||
public double Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
VolumeRequested?.Invoke(this, EventArgs.Empty);
|
||||
return (double)GetValue(VolumeProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(VolumeProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public event EventHandler<SeekRequested> SeekRequested;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public event EventHandler<StateRequested> StateRequested;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public event EventHandler PositionRequested;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public event EventHandler VolumeRequested;
|
||||
|
||||
|
||||
public void Play()
|
||||
{
|
||||
StateRequested?.Invoke(this, new StateRequested(MediaElementState.Playing));
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
StateRequested?.Invoke(this, new StateRequested(MediaElementState.Paused));
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
StateRequested?.Invoke(this, new StateRequested(MediaElementState.Stopped));
|
||||
}
|
||||
|
||||
double IMediaElementController.BufferingProgress { get => (double)GetValue(BufferingProgressProperty); set => SetValue(BufferingProgressProperty, value); }
|
||||
MediaElementState IMediaElementController.CurrentState { get => (MediaElementState)GetValue(CurrentStateProperty); set => SetValue(CurrentStateProperty, value); }
|
||||
TimeSpan? IMediaElementController.Duration { get => (TimeSpan?)GetValue(DurationProperty); set => SetValue(DurationProperty, value); }
|
||||
TimeSpan IMediaElementController.Position { get => (TimeSpan)GetValue(PositionProperty); set => SetValue(PositionProperty, value); }
|
||||
int IMediaElementController.VideoHeight { get => (int)GetValue(VideoHeightProperty); set => SetValue(VideoHeightProperty, value); }
|
||||
int IMediaElementController.VideoWidth { get => (int)GetValue(VideoWidthProperty); set => SetValue(VideoWidthProperty, value); }
|
||||
double IMediaElementController.Volume { get => (double)GetValue(VolumeProperty); set => SetValue(VolumeProperty, value);
|
||||
}
|
||||
void IMediaElementController.OnMediaEnded()
|
||||
{
|
||||
SetValue(CurrentStateProperty, MediaElementState.Stopped);
|
||||
MediaEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler MediaEnded;
|
||||
|
||||
void IMediaElementController.OnMediaFailed()
|
||||
{
|
||||
MediaFailed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler MediaFailed;
|
||||
|
||||
void IMediaElementController.OnMediaOpened()
|
||||
{
|
||||
MediaOpened?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler MediaOpened;
|
||||
|
||||
void IMediaElementController.OnSeekCompleted()
|
||||
{
|
||||
SeekCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler SeekCompleted;
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
if (Source != null)
|
||||
SetInheritedBindingContext(Source, BindingContext);
|
||||
|
||||
base.OnBindingContextChanged();
|
||||
}
|
||||
|
||||
void OnSourceChanged(object sender, EventArgs eventArgs)
|
||||
{
|
||||
OnPropertyChanged(SourceProperty.PropertyName);
|
||||
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
}
|
||||
|
||||
static void OnSourcePropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
|
||||
{
|
||||
((MediaElement)bindable).OnSourcePropertyChanged((MediaSource)oldvalue, (MediaSource)newvalue);
|
||||
}
|
||||
|
||||
void OnSourcePropertyChanged(MediaSource oldvalue, MediaSource newvalue)
|
||||
{
|
||||
if (newvalue != null)
|
||||
{
|
||||
newvalue.SourceChanged += OnSourceChanged;
|
||||
SetInheritedBindingContext(newvalue, BindingContext);
|
||||
}
|
||||
|
||||
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
}
|
||||
|
||||
static void OnSourcePropertyChanging(BindableObject bindable, object oldvalue, object newvalue)
|
||||
{
|
||||
((MediaElement)bindable).OnSourcePropertyChanging((MediaSource)oldvalue, (MediaSource)newvalue);
|
||||
}
|
||||
|
||||
void OnSourcePropertyChanging(MediaSource oldvalue, MediaSource newvalue)
|
||||
{
|
||||
if (oldvalue == null)
|
||||
return;
|
||||
|
||||
oldvalue.SourceChanged -= OnSourceChanged;
|
||||
}
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class SeekRequested : EventArgs
|
||||
{
|
||||
public TimeSpan Position { get; }
|
||||
|
||||
public SeekRequested(TimeSpan position)
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class StateRequested : EventArgs
|
||||
{
|
||||
public MediaElementState State { get; }
|
||||
|
||||
public StateRequested(MediaElementState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMediaElementController
|
||||
{
|
||||
double BufferingProgress { get; set; }
|
||||
MediaElementState CurrentState { get; set; }
|
||||
TimeSpan? Duration { get; set; }
|
||||
TimeSpan Position { get; set; }
|
||||
int VideoHeight { get; set; }
|
||||
int VideoWidth { get; set; }
|
||||
double Volume { get; set; }
|
||||
|
||||
void OnMediaEnded();
|
||||
void OnMediaFailed();
|
||||
void OnMediaOpened();
|
||||
void OnSeekCompleted();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
namespace Xamarin.Forms
|
||||
{
|
||||
public enum MediaElementState
|
||||
{
|
||||
Closed,
|
||||
Opening,
|
||||
Buffering,
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped,
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
[TypeConverter(typeof(MediaSourceConverter))]
|
||||
public abstract class MediaSource : Element
|
||||
{
|
||||
readonly WeakEventManager _weakEventManager = new WeakEventManager();
|
||||
|
||||
public static MediaSource FromFile(string file)
|
||||
{
|
||||
return new FileMediaSource { File = file };
|
||||
}
|
||||
|
||||
public static MediaSource FromUri(Uri uri)
|
||||
{
|
||||
if (!uri.IsAbsoluteUri)
|
||||
throw new ArgumentException("uri is relative");
|
||||
return new UriMediaSource { Uri = uri };
|
||||
}
|
||||
|
||||
public static implicit operator MediaSource(string source)
|
||||
{
|
||||
Uri uri;
|
||||
return Uri.TryCreate(source, UriKind.Absolute, out uri) && uri.Scheme != "file" ? FromUri(uri) : FromFile(source);
|
||||
}
|
||||
|
||||
public static implicit operator MediaSource(Uri uri)
|
||||
{
|
||||
if (uri is null)
|
||||
return null;
|
||||
|
||||
if (!uri.IsAbsoluteUri)
|
||||
throw new ArgumentException("uri is relative");
|
||||
return FromUri(uri);
|
||||
}
|
||||
|
||||
protected void OnSourceChanged()
|
||||
{
|
||||
_weakEventManager.HandleEvent(this, EventArgs.Empty, nameof(SourceChanged));
|
||||
}
|
||||
|
||||
internal event EventHandler SourceChanged
|
||||
{
|
||||
add { _weakEventManager.AddEventHandler(value); }
|
||||
remove { _weakEventManager.RemoveEventHandler(value); }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
[Xaml.TypeConversion(typeof(MediaSource))]
|
||||
public sealed class MediaSourceConverter : TypeConverter
|
||||
{
|
||||
public override object ConvertFromInvariantString(string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Uri uri;
|
||||
return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ? MediaSource.FromUri(uri) : MediaSource.FromFile(value);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(MediaSource)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using XVisualElement = Xamarin.Forms.VisualElement;
|
||||
|
||||
namespace Xamarin.Forms.PlatformConfiguration.TizenSpecific
|
||||
{
|
||||
public interface IVideoOutput
|
||||
{
|
||||
XVisualElement MediaView { get; }
|
||||
View Controller { get; set; }
|
||||
VideoOuputType OuputType { get; }
|
||||
}
|
||||
|
||||
public enum VideoOuputType
|
||||
{
|
||||
Overlay,
|
||||
Buffer,
|
||||
}
|
||||
|
||||
public enum DisplayAspectMode
|
||||
{
|
||||
Fill,
|
||||
AspectFit,
|
||||
AspectFill,
|
||||
OrignalSize
|
||||
}
|
||||
|
||||
public enum PlaybackState
|
||||
{
|
||||
Stopped,
|
||||
Playing,
|
||||
Paused
|
||||
}
|
||||
|
||||
public interface IMediaPlayer
|
||||
{
|
||||
PlaybackState State { get; }
|
||||
|
||||
event EventHandler PlaybackPaused;
|
||||
event EventHandler PlaybackStarted;
|
||||
event EventHandler PlaybackStopped;
|
||||
|
||||
Task<bool> Start();
|
||||
void Stop();
|
||||
void Pause();
|
||||
}
|
||||
|
||||
public interface IPlatformMediaPlayer : IDisposable
|
||||
{
|
||||
bool UsesEmbeddingControls { get; set; }
|
||||
bool AutoPlay { get; set; }
|
||||
bool AutoStop { get; set; }
|
||||
double Volume { get; set; }
|
||||
bool IsMuted { get; set; }
|
||||
bool IsLooping { get; set; }
|
||||
int Position { get; }
|
||||
int Duration { get; }
|
||||
DisplayAspectMode AspectMode { get; set; }
|
||||
|
||||
event EventHandler PlaybackCompleted;
|
||||
event EventHandler PlaybackStarted;
|
||||
event EventHandler PlaybackPaused;
|
||||
event EventHandler PlaybackStopped;
|
||||
event EventHandler UpdateStreamInfo;
|
||||
event EventHandler<BufferingProgressUpdatedEventArgs> BufferingProgressUpdated;
|
||||
event EventHandler ErrorOccurred;
|
||||
|
||||
void SetDisplay(IVideoOutput output);
|
||||
void SetSource(MediaSource source);
|
||||
Task<bool> Start();
|
||||
void Stop();
|
||||
void Pause();
|
||||
Task<int> Seek(int ms);
|
||||
Task<Stream> GetAlbumArts();
|
||||
Task<IDictionary<string, string>> GetMetadata();
|
||||
Task<Size> GetVideoSize();
|
||||
View GetEmbeddingControlView(IMediaPlayer player);
|
||||
}
|
||||
|
||||
public class BufferingProgressUpdatedEventArgs : EventArgs
|
||||
{
|
||||
public double Progress { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
public sealed class UriMediaSource : MediaSource
|
||||
{
|
||||
public static readonly BindableProperty UriProperty = BindableProperty.Create(nameof(Uri), typeof(Uri), typeof(UriMediaSource), default(Uri),
|
||||
propertyChanged: (bindable, oldvalue, newvalue) => ((UriMediaSource)bindable).OnSourceChanged(), validateValue: (bindable, value) => value == null || ((Uri)value).IsAbsoluteUri);
|
||||
|
||||
|
||||
[TypeConverter(typeof(UriTypeConverter))]
|
||||
public Uri Uri
|
||||
{
|
||||
get { return (Uri)GetValue(UriProperty); }
|
||||
set { SetValue(UriProperty, value); }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Uri: {Uri}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,537 +0,0 @@
|
|||
using Android.Content;
|
||||
using Android.Media;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.Platform.Android.FastRenderers;
|
||||
using AView = Android.Views.View;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
public sealed class MediaElementRenderer : FrameLayout, IVisualElementRenderer, IViewRenderer, IEffectControlProvider, MediaPlayer.IOnCompletionListener, MediaPlayer.IOnInfoListener, MediaPlayer.IOnPreparedListener, MediaPlayer.IOnErrorListener
|
||||
{
|
||||
bool _isDisposed;
|
||||
int? _defaultLabelFor;
|
||||
MediaElement MediaElement { get; set; }
|
||||
IMediaElementController Controller => MediaElement as IMediaElementController;
|
||||
|
||||
readonly AutomationPropertiesProvider _automationPropertiesProvider;
|
||||
readonly EffectControlProvider _effectControlProvider;
|
||||
VisualElementTracker _tracker;
|
||||
|
||||
MediaController _controller;
|
||||
MediaPlayer _mediaPlayer;
|
||||
FormsVideoView _view;
|
||||
|
||||
public MediaElementRenderer(Context context) : base(context)
|
||||
{
|
||||
Xamarin.Forms.MediaElement.VerifyMediaElementFlagEnabled(nameof(MediaElementRenderer));
|
||||
_automationPropertiesProvider = new AutomationPropertiesProvider(this);
|
||||
_effectControlProvider = new EffectControlProvider(this);
|
||||
|
||||
_view = new FormsVideoView(Context);
|
||||
_view.SetZOrderMediaOverlay(true);
|
||||
_view.SetOnCompletionListener(this);
|
||||
_view.SetOnInfoListener(this);
|
||||
_view.SetOnPreparedListener(this);
|
||||
_view.SetOnErrorListener(this);
|
||||
_view.MetadataRetrieved += MetadataRetrieved;
|
||||
|
||||
AddView(_view, -1, -1);
|
||||
|
||||
_controller = new MediaController(Context);
|
||||
_controller.SetAnchorView(this);
|
||||
_view.SetMediaController(_controller);
|
||||
}
|
||||
|
||||
public VisualElement Element => MediaElement;
|
||||
|
||||
VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
|
||||
|
||||
ViewGroup IVisualElementRenderer.ViewGroup => null;
|
||||
|
||||
AView IVisualElementRenderer.View => this;
|
||||
|
||||
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
|
||||
public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
|
||||
|
||||
SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
|
||||
{
|
||||
AView view = this;
|
||||
view.Measure(widthConstraint, heightConstraint);
|
||||
|
||||
return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), new Size());
|
||||
}
|
||||
|
||||
void IViewRenderer.MeasureExactly()
|
||||
{
|
||||
ViewRenderer.MeasureExactly(this, Element, Context);
|
||||
}
|
||||
|
||||
void UnsubscribeFromEvents(MediaElement element)
|
||||
{
|
||||
if (element == null)
|
||||
return;
|
||||
|
||||
element.PropertyChanged -= OnElementPropertyChanged;
|
||||
element.SeekRequested -= SeekRequested;
|
||||
element.StateRequested -= StateRequested;
|
||||
element.PositionRequested -= OnPositionRequested;
|
||||
|
||||
}
|
||||
|
||||
void IVisualElementRenderer.SetElement(VisualElement element)
|
||||
{
|
||||
if (element is null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
if (!(element is MediaElement))
|
||||
throw new ArgumentException($"{nameof(element)} must be of type {nameof(MediaElement)}");
|
||||
|
||||
MediaElement oldElement = MediaElement;
|
||||
MediaElement = (MediaElement)element;
|
||||
|
||||
Performance.Start(out string reference);
|
||||
|
||||
if (oldElement != null)
|
||||
{
|
||||
UnsubscribeFromEvents(oldElement);
|
||||
}
|
||||
|
||||
Color currentColor = oldElement?.BackgroundColor ?? Color.Default;
|
||||
if (element.BackgroundColor != currentColor)
|
||||
{
|
||||
UpdateBackgroundColor();
|
||||
}
|
||||
|
||||
if (MediaElement != null)
|
||||
{
|
||||
MediaElement.PropertyChanged += OnElementPropertyChanged;
|
||||
MediaElement.SeekRequested += SeekRequested;
|
||||
MediaElement.StateRequested += StateRequested;
|
||||
MediaElement.PositionRequested += OnPositionRequested;
|
||||
}
|
||||
|
||||
if (_tracker is null)
|
||||
{
|
||||
// Can't set up the tracker in the constructor because it access the Element (for now)
|
||||
SetTracker(new VisualElementTracker(this));
|
||||
}
|
||||
|
||||
OnElementChanged(new ElementChangedEventArgs<MediaElement>(oldElement as MediaElement, MediaElement));
|
||||
|
||||
EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
|
||||
|
||||
Performance.Stop(reference);
|
||||
}
|
||||
|
||||
void StateRequested(object sender, StateRequested e)
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
switch (e.State)
|
||||
{
|
||||
case MediaElementState.Playing:
|
||||
_view.Start();
|
||||
Controller.CurrentState = _view.IsPlaying ? MediaElementState.Playing : MediaElementState.Stopped;
|
||||
break;
|
||||
|
||||
case MediaElementState.Paused:
|
||||
if (_view.CanPause())
|
||||
{
|
||||
_view.Pause();
|
||||
Controller.CurrentState = MediaElementState.Paused;
|
||||
}
|
||||
break;
|
||||
|
||||
case MediaElementState.Stopped:
|
||||
_view.Pause();
|
||||
_view.SeekTo(0);
|
||||
|
||||
Controller.CurrentState = _view.IsPlaying ? MediaElementState.Playing : MediaElementState.Stopped;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateLayoutParameters();
|
||||
Controller.Position = _view.Position;
|
||||
}
|
||||
|
||||
void OnPositionRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
Controller.Position = _view.Position;
|
||||
}
|
||||
|
||||
void SeekRequested(object sender, SeekRequested e)
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
Controller.Position = _view.Position;
|
||||
}
|
||||
|
||||
void IVisualElementRenderer.SetLabelFor(int? id)
|
||||
{
|
||||
if (_defaultLabelFor is null)
|
||||
{
|
||||
_defaultLabelFor = LabelFor;
|
||||
}
|
||||
|
||||
LabelFor = (int)(id ?? _defaultLabelFor);
|
||||
}
|
||||
|
||||
void SetTracker(VisualElementTracker tracker)
|
||||
{
|
||||
_tracker = tracker;
|
||||
}
|
||||
|
||||
void UpdateBackgroundColor()
|
||||
{
|
||||
SetBackgroundColor(Element.BackgroundColor.ToAndroid());
|
||||
}
|
||||
|
||||
void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
ReleaseControl();
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
SetOnClickListener(null);
|
||||
SetOnTouchListener(null);
|
||||
|
||||
_automationPropertiesProvider?.Dispose();
|
||||
_tracker?.Dispose();
|
||||
|
||||
if (Element != null)
|
||||
{
|
||||
UnsubscribeFromEvents(Element as MediaElement);
|
||||
|
||||
if (Platform.GetRenderer(Element) == this)
|
||||
Element.ClearValue(Platform.RendererProperty);
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//todo: make virtual when unsealed
|
||||
void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
|
||||
{
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
this.EnsureId();
|
||||
|
||||
UpdateKeepScreenOn();
|
||||
UpdateLayoutParameters();
|
||||
UpdateShowPlaybackControls();
|
||||
UpdateSource();
|
||||
UpdateBackgroundColor();
|
||||
|
||||
ElevationHelper.SetElevation(this, e.NewElement);
|
||||
}
|
||||
|
||||
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
|
||||
}
|
||||
|
||||
void MetadataRetrieved(object sender, EventArgs e)
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
Controller.Duration = _view.DurationTimeSpan;
|
||||
Controller.VideoHeight = _view.VideoHeight;
|
||||
Controller.VideoWidth = _view.VideoWidth;
|
||||
|
||||
Device.BeginInvokeOnMainThread(UpdateLayoutParameters);
|
||||
}
|
||||
|
||||
//todo: make virtual when unsealed
|
||||
void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch(e.PropertyName)
|
||||
{
|
||||
case nameof(MediaElement.Aspect):
|
||||
UpdateLayoutParameters();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.IsLooping):
|
||||
if (_mediaPlayer != null)
|
||||
{
|
||||
_mediaPlayer.Looping = MediaElement.IsLooping;
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.KeepScreenOn):
|
||||
UpdateKeepScreenOn();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.ShowsPlaybackControls):
|
||||
UpdateShowPlaybackControls();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Source):
|
||||
UpdateSource();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Volume):
|
||||
_mediaPlayer?.SetVolume((float)MediaElement.Volume, (float)MediaElement.Volume);
|
||||
break;
|
||||
}
|
||||
|
||||
ElementPropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public void RegisterEffect(Effect effect)
|
||||
{
|
||||
_effectControlProvider.RegisterEffect(effect);
|
||||
}
|
||||
|
||||
void UpdateKeepScreenOn()
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
_view.KeepScreenOn = MediaElement.KeepScreenOn;
|
||||
}
|
||||
|
||||
void UpdateShowPlaybackControls()
|
||||
{
|
||||
if (_controller == null)
|
||||
return;
|
||||
|
||||
_controller.Visibility = MediaElement.ShowsPlaybackControls ? ViewStates.Visible : ViewStates.Gone;
|
||||
}
|
||||
|
||||
void UpdateSource()
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
if (MediaElement.Source != null)
|
||||
{
|
||||
if (MediaElement.Source is UriMediaSource uriSource)
|
||||
{
|
||||
if (uriSource.Uri.Scheme == "ms-appx")
|
||||
{
|
||||
if (uriSource.Uri.LocalPath.Length <= 1)
|
||||
return;
|
||||
|
||||
// video resources should be in the raw folder with Build Action set to AndroidResource
|
||||
string uri = "android.resource://" + Context.PackageName + "/raw/" + uriSource.Uri.LocalPath.Substring(1, uriSource.Uri.LocalPath.LastIndexOf('.') - 1).ToLower();
|
||||
_view.SetVideoURI(global::Android.Net.Uri.Parse(uri));
|
||||
}
|
||||
else if (uriSource.Uri.Scheme == "ms-appdata")
|
||||
{
|
||||
string filePath = Platform.ResolveMsAppDataUri(uriSource.Uri);
|
||||
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
throw new ArgumentException("Invalid Uri", "Source");
|
||||
|
||||
_view.SetVideoPath(filePath);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uriSource.Uri.IsFile)
|
||||
{
|
||||
_view.SetVideoPath(uriSource.Uri.AbsolutePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_view.SetVideoURI(global::Android.Net.Uri.Parse(uriSource.Uri.AbsoluteUri));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (MediaElement.Source is FileMediaSource fileSource)
|
||||
{
|
||||
_view.SetVideoPath(fileSource.File);
|
||||
}
|
||||
|
||||
if (MediaElement.AutoPlay)
|
||||
{
|
||||
_view.Start();
|
||||
Controller.CurrentState = _view.IsPlaying ? MediaElementState.Playing : MediaElementState.Stopped;
|
||||
}
|
||||
|
||||
}
|
||||
else if (_view.IsPlaying)
|
||||
{
|
||||
_view.StopPlayback();
|
||||
Controller.CurrentState = MediaElementState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer.IOnCompletionListener.OnCompletion(MediaPlayer mp)
|
||||
{
|
||||
if (Controller == null)
|
||||
return;
|
||||
|
||||
Controller.Position = TimeSpan.FromMilliseconds(_mediaPlayer.CurrentPosition);
|
||||
Controller.OnMediaEnded();
|
||||
}
|
||||
|
||||
void MediaPlayer.IOnPreparedListener.OnPrepared(MediaPlayer mp)
|
||||
{
|
||||
if (Controller == null)
|
||||
return;
|
||||
|
||||
Controller.OnMediaOpened();
|
||||
|
||||
UpdateLayoutParameters();
|
||||
|
||||
_mediaPlayer = mp;
|
||||
mp.Looping = MediaElement.IsLooping;
|
||||
mp.SeekTo(0);
|
||||
|
||||
if (MediaElement.AutoPlay)
|
||||
{
|
||||
_mediaPlayer.Start();
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
}
|
||||
else
|
||||
{
|
||||
Controller.CurrentState = MediaElementState.Paused;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateLayoutParameters()
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
if (_view.VideoWidth == 0 || _view.VideoHeight == 0)
|
||||
{
|
||||
_view.LayoutParameters = new FrameLayout.LayoutParams(Width, Height, GravityFlags.Fill);
|
||||
return;
|
||||
}
|
||||
|
||||
float ratio = (float)_view.VideoWidth / (float)_view.VideoHeight;
|
||||
float controlRatio = (float)Width / Height;
|
||||
|
||||
switch (MediaElement.Aspect)
|
||||
{
|
||||
case Aspect.Fill:
|
||||
// TODO: this doesn't stretch like other platforms...
|
||||
_view.LayoutParameters = new FrameLayout.LayoutParams(Width, Height, GravityFlags.Fill) { LeftMargin = 0, RightMargin = 0, TopMargin = 0, BottomMargin = 0 };
|
||||
break;
|
||||
|
||||
case Aspect.AspectFit:
|
||||
if (ratio > controlRatio)
|
||||
{
|
||||
int requiredHeight = (int)(Width / ratio);
|
||||
int vertMargin = (Height - requiredHeight) / 2;
|
||||
_view.LayoutParameters = new FrameLayout.LayoutParams(Width, requiredHeight, GravityFlags.FillHorizontal | GravityFlags.CenterVertical) { LeftMargin = 0, RightMargin = 0, TopMargin = vertMargin, BottomMargin = vertMargin };
|
||||
}
|
||||
else
|
||||
{
|
||||
int requiredWidth = (int)(Height * ratio);
|
||||
int horizMargin = (Width - requiredWidth) / 2;
|
||||
_view.LayoutParameters = new FrameLayout.LayoutParams(requiredWidth, Height, GravityFlags.CenterHorizontal | GravityFlags.FillVertical) { LeftMargin = horizMargin, RightMargin = horizMargin, TopMargin = 0, BottomMargin = 0 };
|
||||
}
|
||||
break;
|
||||
|
||||
case Aspect.AspectFill:
|
||||
if (ratio > controlRatio)
|
||||
{
|
||||
int requiredWidth = (int)(Height * ratio);
|
||||
int horizMargin = (Width - requiredWidth) / 2;
|
||||
_view.LayoutParameters = new FrameLayout.LayoutParams((int)(Height * ratio), Height, GravityFlags.CenterHorizontal | GravityFlags.FillVertical) { TopMargin = 0, BottomMargin = 0, LeftMargin = horizMargin, RightMargin = horizMargin };
|
||||
}
|
||||
else
|
||||
{
|
||||
int requiredHeight = (int)(Width / ratio);
|
||||
int vertMargin = (Height - requiredHeight) / 2;
|
||||
_view.LayoutParameters = new FrameLayout.LayoutParams(Width, requiredHeight, GravityFlags.FillHorizontal | GravityFlags.CenterVertical) { LeftMargin = 0, RightMargin = 0, TopMargin = vertMargin, BottomMargin = vertMargin };
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ReleaseControl()
|
||||
{
|
||||
if (_view != null)
|
||||
{
|
||||
_view.MetadataRetrieved -= MetadataRetrieved;
|
||||
RemoveView(_view);
|
||||
_view.SetOnPreparedListener(null);
|
||||
_view.SetOnCompletionListener(null);
|
||||
_view.Dispose();
|
||||
_view = null;
|
||||
}
|
||||
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.Dispose();
|
||||
_controller = null;
|
||||
}
|
||||
|
||||
if (_mediaPlayer != null)
|
||||
{
|
||||
_mediaPlayer.Dispose();
|
||||
_mediaPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaPlayer.IOnErrorListener.OnError(MediaPlayer mp, MediaError what, int extra)
|
||||
{
|
||||
if (Controller == null)
|
||||
return false;
|
||||
|
||||
Controller.OnMediaFailed();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MediaPlayer.IOnInfoListener.OnInfo(MediaPlayer mp, MediaInfo what, int extra)
|
||||
{
|
||||
if (_view == null)
|
||||
return false;
|
||||
|
||||
switch (what)
|
||||
{
|
||||
case MediaInfo.BufferingStart:
|
||||
Controller.CurrentState = MediaElementState.Buffering;
|
||||
mp.BufferingUpdate += Mp_BufferingUpdate;
|
||||
break;
|
||||
|
||||
case MediaInfo.BufferingEnd:
|
||||
mp.BufferingUpdate -= Mp_BufferingUpdate;
|
||||
Controller.CurrentState = MediaElementState.Paused;
|
||||
break;
|
||||
|
||||
case MediaInfo.VideoRenderingStart:
|
||||
_view.SetBackground(null);
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
break;
|
||||
}
|
||||
|
||||
_mediaPlayer = mp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mp_BufferingUpdate(object sender, MediaPlayer.BufferingUpdateEventArgs e)
|
||||
{
|
||||
Controller.BufferingProgress = e.Percent / 100f;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,6 @@ using Xamarin.Forms.Platform.Android;
|
|||
[assembly: ExportRenderer (typeof (CarouselPage), typeof (CarouselPageRenderer))]
|
||||
[assembly: ExportRenderer (typeof (Page), typeof (PageRenderer))]
|
||||
[assembly: ExportRenderer (typeof (MasterDetailPage), typeof (MasterDetailRenderer))]
|
||||
[assembly: ExportRenderer (typeof (MediaElement), typeof(MediaElementRenderer))]
|
||||
[assembly: ExportRenderer (typeof (RefreshView), typeof (RefreshViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(Path), typeof(PathRenderer))]
|
||||
#endif
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
using Tizen.Multimedia;
|
||||
using Xamarin.Forms.PlatformConfiguration.TizenSpecific;
|
||||
using ERect = ElmSharp.Rect;
|
||||
using MRect = Tizen.Multimedia.Rectangle;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Tizen
|
||||
{
|
||||
public static class MediaElementExtension
|
||||
{
|
||||
public static MRect ToMultimediaRectangle(this ERect rect)
|
||||
{
|
||||
return new MRect(rect.X, rect.Y, rect.Width, rect.Height);
|
||||
}
|
||||
|
||||
public static PlayerDisplayMode ToNative(this Aspect aspect)
|
||||
{
|
||||
PlayerDisplayMode ret = PlayerDisplayMode.LetterBox;
|
||||
switch (aspect)
|
||||
{
|
||||
case Aspect.AspectFill:
|
||||
ret = PlayerDisplayMode.CroppedFull;
|
||||
break;
|
||||
case Aspect.AspectFit:
|
||||
ret = PlayerDisplayMode.LetterBox;
|
||||
break;
|
||||
case Aspect.Fill:
|
||||
ret = PlayerDisplayMode.FullScreen;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static PlayerDisplayMode ToNative(this DisplayAspectMode mode)
|
||||
{
|
||||
PlayerDisplayMode ret = PlayerDisplayMode.LetterBox;
|
||||
switch (mode)
|
||||
{
|
||||
case DisplayAspectMode.AspectFill:
|
||||
ret = PlayerDisplayMode.CroppedFull;
|
||||
break;
|
||||
case DisplayAspectMode.AspectFit:
|
||||
ret = PlayerDisplayMode.LetterBox;
|
||||
break;
|
||||
case DisplayAspectMode.Fill:
|
||||
ret = PlayerDisplayMode.FullScreen;
|
||||
break;
|
||||
case DisplayAspectMode.OrignalSize:
|
||||
ret = PlayerDisplayMode.OriginalOrFull;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static DisplayAspectMode ToDisplayAspectMode(this Aspect aspect)
|
||||
{
|
||||
DisplayAspectMode ret = DisplayAspectMode.AspectFit;
|
||||
switch (aspect)
|
||||
{
|
||||
case Aspect.AspectFill:
|
||||
ret = DisplayAspectMode.AspectFill;
|
||||
break;
|
||||
case Aspect.AspectFit:
|
||||
ret = DisplayAspectMode.AspectFit;
|
||||
break;
|
||||
case Aspect.Fill:
|
||||
ret = DisplayAspectMode.Fill;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms.PlatformConfiguration.TizenSpecific;
|
||||
|
||||
using XLabel = Xamarin.Forms.Label;
|
||||
using XTextAlignment = Xamarin.Forms.TextAlignment;
|
||||
using PlayerState = Xamarin.Forms.PlatformConfiguration.TizenSpecific.PlaybackState;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Tizen.Native
|
||||
{
|
||||
public class EmbeddingControls : ContentView
|
||||
{
|
||||
public ImageButton PlayImage { get; private set; }
|
||||
public ImageButton PauseImage { get; private set; }
|
||||
|
||||
public EmbeddingControls()
|
||||
{
|
||||
PlayImage = new ImageButton
|
||||
{
|
||||
Source = ImageSource.FromResource(ThemeConstants.MediaPlayer.Resources.PlayImagePath, typeof(EmbeddingControls).Assembly),
|
||||
IsVisible = false
|
||||
};
|
||||
PlayImage.Clicked += OnImageButtonClicked;
|
||||
AbsoluteLayout.SetLayoutFlags(PlayImage, AbsoluteLayoutFlags.All);
|
||||
AbsoluteLayout.SetLayoutBounds(PlayImage, new Rectangle(0.5, 0.5, 0.25, 0.25));
|
||||
|
||||
PauseImage = new ImageButton
|
||||
{
|
||||
Source = ImageSource.FromResource(ThemeConstants.MediaPlayer.Resources.PauseImagePath, typeof(EmbeddingControls).Assembly),
|
||||
IsVisible = false
|
||||
};
|
||||
PauseImage.Clicked += OnImageButtonClicked;
|
||||
AbsoluteLayout.SetLayoutFlags(PauseImage, AbsoluteLayoutFlags.All);
|
||||
AbsoluteLayout.SetLayoutBounds(PauseImage, new Rectangle(0.5, 0.5, 0.25, 0.25));
|
||||
|
||||
var bufferingLabel = new XLabel
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label), false),
|
||||
HorizontalTextAlignment = XTextAlignment.Center,
|
||||
TextColor = ThemeConstants.MediaPlayer.ColorClass.DefaultProgressLabelColor
|
||||
};
|
||||
bufferingLabel.SetBinding(XLabel.TextProperty, new Binding
|
||||
{
|
||||
Path = "BufferingProgress",
|
||||
StringFormat = "{0:0%}"
|
||||
});
|
||||
bufferingLabel.SetBinding(IsVisibleProperty, new Binding
|
||||
{
|
||||
Path = "IsBuffering",
|
||||
});
|
||||
AbsoluteLayout.SetLayoutFlags(bufferingLabel, AbsoluteLayoutFlags.All);
|
||||
AbsoluteLayout.SetLayoutBounds(bufferingLabel, new Rectangle(0.5, 0.5, 0.25, 0.25));
|
||||
|
||||
var progressBoxView = new BoxView
|
||||
{
|
||||
Color = ThemeConstants.MediaPlayer.ColorClass.DefaultProgressBarColor
|
||||
};
|
||||
progressBoxView.SetBinding(AbsoluteLayout.LayoutBoundsProperty, new Binding
|
||||
{
|
||||
Path = "Progress",
|
||||
Converter = new ProgressToBoundTextConverter()
|
||||
});
|
||||
AbsoluteLayout.SetLayoutFlags(progressBoxView, AbsoluteLayoutFlags.All);
|
||||
|
||||
var posLabel = new XLabel
|
||||
{
|
||||
Margin = new Thickness(10, 0, 0, 0),
|
||||
FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(XLabel)),
|
||||
HorizontalTextAlignment = XTextAlignment.Start
|
||||
};
|
||||
posLabel.SetBinding(XLabel.TextProperty, new Binding
|
||||
{
|
||||
Path = "Position",
|
||||
Converter = new MillisecondToTextConverter()
|
||||
});
|
||||
AbsoluteLayout.SetLayoutFlags(posLabel, AbsoluteLayoutFlags.All);
|
||||
AbsoluteLayout.SetLayoutBounds(posLabel, new Rectangle(0,0,1,1));
|
||||
|
||||
var durationLabel = new XLabel
|
||||
{
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(XLabel)),
|
||||
HorizontalTextAlignment = XTextAlignment.End
|
||||
};
|
||||
durationLabel.SetBinding(XLabel.TextProperty, new Binding
|
||||
{
|
||||
Path = "Duration",
|
||||
Converter = new MillisecondToTextConverter()
|
||||
});
|
||||
AbsoluteLayout.SetLayoutFlags(durationLabel, AbsoluteLayoutFlags.All);
|
||||
AbsoluteLayout.SetLayoutBounds(durationLabel, new Rectangle(0, 0, 1, 1));
|
||||
|
||||
var progressInnerLayout = new AbsoluteLayout
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
HeightRequest = 23,
|
||||
BackgroundColor = ThemeConstants.MediaPlayer.ColorClass.DefaultProgressAreaColor,
|
||||
Children =
|
||||
{
|
||||
progressBoxView,
|
||||
posLabel,
|
||||
durationLabel
|
||||
}
|
||||
};
|
||||
|
||||
var progressLayout = new StackLayout
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new StackLayout { VerticalOptions = LayoutOptions.FillAndExpand },
|
||||
new StackLayout
|
||||
{
|
||||
Margin = Device.Idiom == TargetIdiom.Watch ? new Thickness(80, 0, 80, 0) : 20,
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
BackgroundColor = ThemeConstants.MediaPlayer.ColorClass.DefaultProgressAreaBackgroundColor,
|
||||
Children = { progressInnerLayout }
|
||||
}
|
||||
}
|
||||
};
|
||||
AbsoluteLayout.SetLayoutFlags(progressLayout, AbsoluteLayoutFlags.All);
|
||||
AbsoluteLayout.SetLayoutBounds(progressLayout, new Rectangle(0, 0, 1, 1));
|
||||
|
||||
Content = new AbsoluteLayout
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
Children = {
|
||||
progressLayout,
|
||||
PlayImage,
|
||||
PauseImage,
|
||||
bufferingLabel
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
if (BindingContext is IMediaPlayer player)
|
||||
{
|
||||
player.PlaybackPaused += OnPlaybackStateChanged;
|
||||
player.PlaybackStarted += OnPlaybackStateChanged;
|
||||
player.PlaybackStopped += OnPlaybackStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
async void OnPlaybackStateChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (BindingContext is IMediaPlayer player)
|
||||
{
|
||||
if (player.State == PlayerState.Playing)
|
||||
{
|
||||
var unused = PlayImage.FadeTo(0, 100);
|
||||
await PlayImage.ScaleTo(3.0, 300);
|
||||
PlayImage.IsVisible = false;
|
||||
PlayImage.Scale = 1.0;
|
||||
|
||||
PauseImage.IsVisible = true;
|
||||
unused = PauseImage.FadeTo(1, 50);
|
||||
}
|
||||
else
|
||||
{
|
||||
var unused = PauseImage.FadeTo(0, 100);
|
||||
await PauseImage.ScaleTo(3.0, 300);
|
||||
PauseImage.IsVisible = false;
|
||||
PauseImage.Scale = 1.0;
|
||||
|
||||
PlayImage.IsVisible = true;
|
||||
unused = PlayImage.FadeTo(1, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void OnImageButtonClicked(object sender, EventArgs e)
|
||||
{
|
||||
if (BindingContext is IMediaPlayer player)
|
||||
{
|
||||
if (player.State == PlayerState.Playing)
|
||||
{
|
||||
player.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
await player.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProgressToBoundTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
double progress = (double)value;
|
||||
if (Double.IsNaN(progress))
|
||||
{
|
||||
progress = 0d;
|
||||
}
|
||||
return new Rectangle(0, 0, progress, 1);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Rectangle rect = (Rectangle)value;
|
||||
return rect.Width;
|
||||
}
|
||||
}
|
||||
|
||||
public class MillisecondToTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
int millisecond = (int)value;
|
||||
int second = (millisecond / 1000) % 60;
|
||||
int min = (millisecond / 1000 / 60) % 60;
|
||||
int hour = (millisecond / 1000 / 60 / 60);
|
||||
if (hour > 0)
|
||||
{
|
||||
return string.Format("{0:d2}:{1:d2}:{2:d2}", hour, min, second);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format("{0:d2}:{1:d2}", min, second);
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
using System;
|
||||
using ElmSharp;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Tizen.Native
|
||||
{
|
||||
public class LayoutCanvas : WidgetLayout, IContainable<EvasObject>
|
||||
{
|
||||
readonly ObservableCollection<EvasObject> _children = new ObservableCollection<EvasObject>();
|
||||
Box _box;
|
||||
|
||||
public LayoutCanvas(EvasObject parent) : base(parent)
|
||||
{
|
||||
_box = new Box(parent);
|
||||
SetContent(_box);
|
||||
|
||||
_children.CollectionChanged += (o, e) =>
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
foreach (var v in e.NewItems)
|
||||
{
|
||||
var view = v as EvasObject;
|
||||
if (null != view)
|
||||
{
|
||||
OnAdd(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
foreach (var v in e.OldItems)
|
||||
{
|
||||
if (v is EvasObject view)
|
||||
{
|
||||
OnRemove(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
OnRemoveAll();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public event EventHandler<LayoutEventArgs> LayoutUpdated
|
||||
{
|
||||
add { _box.LayoutUpdated += value; }
|
||||
remove { _box.LayoutUpdated -= value; }
|
||||
}
|
||||
|
||||
public new IList<EvasObject> Children
|
||||
{
|
||||
get
|
||||
{
|
||||
return _children;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUnrealize()
|
||||
{
|
||||
foreach (var child in _children)
|
||||
{
|
||||
child.Unrealize();
|
||||
}
|
||||
|
||||
base.OnUnrealize();
|
||||
}
|
||||
|
||||
void OnAdd(EvasObject view)
|
||||
{
|
||||
_box.PackEnd(view);
|
||||
}
|
||||
|
||||
void OnRemove(EvasObject view)
|
||||
{
|
||||
_box.UnPack(view);
|
||||
}
|
||||
|
||||
void OnRemoveAll()
|
||||
{
|
||||
_box.UnPackAll();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,545 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.PlatformConfiguration.TizenSpecific;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Tizen.Native
|
||||
{
|
||||
public class MediaPlayer : Element, IMediaPlayer, IDisposable
|
||||
{
|
||||
public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(MediaSource), typeof(MediaPlayer), default(MediaSource), propertyChanged: OnSourceChanged);
|
||||
|
||||
public static readonly BindableProperty VideoOutputProperty = BindableProperty.Create(nameof(VideoOutput), typeof(IVideoOutput), typeof(MediaPlayer), null, propertyChanging: null, propertyChanged: (b, o, n) => ((MediaPlayer)b).OnVideoOutputChanged());
|
||||
|
||||
public static readonly BindableProperty UsesEmbeddingControlsProperty = BindableProperty.Create(nameof(UsesEmbeddingControls), typeof(bool), typeof(MediaPlayer), true, propertyChanged: (b, o, n) => ((MediaPlayer)b).OnUsesEmbeddingControlsChanged());
|
||||
|
||||
public static readonly BindableProperty VolumeProperty = BindableProperty.Create(nameof(Volume), typeof(double), typeof(MediaPlayer), 1d, coerceValue: (bindable, value) => ((double)value).Clamp(0, 1), propertyChanged: (b, o, n) => ((MediaPlayer)b).OnVolumeChanged());
|
||||
|
||||
public static readonly BindableProperty IsMutedProperty = BindableProperty.Create(nameof(IsMuted), typeof(bool), typeof(MediaPlayer), false, propertyChanged: (b, o, n) => ((MediaPlayer)b).UpdateIsMuted());
|
||||
|
||||
public static readonly BindableProperty AspectModeProperty = BindableProperty.Create(nameof(AspectMode), typeof(DisplayAspectMode), typeof(MediaPlayer), DisplayAspectMode.AspectFit, propertyChanged: (b, o, n) => ((MediaPlayer)b).OnAspectModeChanged());
|
||||
|
||||
public static readonly BindableProperty AutoPlayProperty = BindableProperty.Create(nameof(AutoPlay), typeof(bool), typeof(MediaPlayer), false, propertyChanged: (b, o, n) => ((MediaPlayer)b).UpdateAutoPlay());
|
||||
|
||||
public static readonly BindableProperty AutoStopProperty = BindableProperty.Create(nameof(AutoStop), typeof(bool), typeof(MediaPlayer), true, propertyChanged: (b, o, n) => ((MediaPlayer)b).UpdateAutoStop());
|
||||
|
||||
public static readonly BindableProperty IsLoopingProperty = BindableProperty.Create(nameof(IsLooping), typeof(bool), typeof(MediaPlayer), false, propertyChanged: (b, o, n) => ((MediaPlayer)b).UpdateIsLooping());
|
||||
|
||||
static readonly BindablePropertyKey DurationPropertyKey = BindableProperty.CreateReadOnly(nameof(Duration), typeof(int), typeof(MediaPlayer), 0);
|
||||
|
||||
public static readonly BindableProperty DurationProperty = DurationPropertyKey.BindableProperty;
|
||||
|
||||
static readonly BindablePropertyKey BufferingProgressPropertyKey = BindableProperty.CreateReadOnly(nameof(BufferingProgress), typeof(double), typeof(MediaPlayer), 0d);
|
||||
|
||||
public static readonly BindableProperty BufferingProgressProperty = BufferingProgressPropertyKey.BindableProperty;
|
||||
|
||||
static readonly BindablePropertyKey PositionPropertyKey = BindableProperty.CreateReadOnly(nameof(Position), typeof(int), typeof(MediaPlayer), 0);
|
||||
|
||||
public static readonly BindableProperty PositionProperty = PositionPropertyKey.BindableProperty;
|
||||
|
||||
static readonly BindablePropertyKey StatePropertyKey = BindableProperty.CreateReadOnly(nameof(State), typeof(PlaybackState), typeof(MediaPlayer), PlaybackState.Stopped);
|
||||
|
||||
public static readonly BindableProperty StateProperty = StatePropertyKey.BindableProperty;
|
||||
|
||||
public static readonly BindableProperty PositionUpdateIntervalProperty = BindableProperty.Create(nameof(PositionUpdateInterval), typeof(int), typeof(MediaPlayer), 500);
|
||||
|
||||
static readonly BindablePropertyKey IsBufferingPropertyKey = BindableProperty.CreateReadOnly(nameof(IsBuffering), typeof(bool), typeof(MediaPlayer), false);
|
||||
|
||||
public static readonly BindableProperty IsBufferingProperty = IsBufferingPropertyKey.BindableProperty;
|
||||
|
||||
bool _disposed = false;
|
||||
bool _isPlaying;
|
||||
bool _controlsAlwaysVisible;
|
||||
IPlatformMediaPlayer _impl;
|
||||
CancellationTokenSource _hideTimerCTS = new CancellationTokenSource();
|
||||
Lazy<View> _controls;
|
||||
|
||||
public MediaPlayer()
|
||||
{
|
||||
_impl = new MediaPlayerImpl();
|
||||
_impl.UpdateStreamInfo += OnUpdateStreamInfo;
|
||||
_impl.PlaybackCompleted += SendPlaybackCompleted;
|
||||
_impl.PlaybackStarted += SendPlaybackStarted;
|
||||
_impl.PlaybackPaused += SendPlaybackPaused;
|
||||
_impl.PlaybackStopped += SendPlaybackStopped;
|
||||
_impl.BufferingProgressUpdated += OnUpdateBufferingProgress;
|
||||
_impl.ErrorOccurred += OnErrorOccurred;
|
||||
_impl.UsesEmbeddingControls = true;
|
||||
_impl.Volume = 1d;
|
||||
_impl.AspectMode = DisplayAspectMode.AspectFit;
|
||||
_impl.AutoPlay = false;
|
||||
_impl.AutoStop = true;
|
||||
|
||||
_controlsAlwaysVisible = false;
|
||||
_controls = new Lazy<View>(() =>
|
||||
{
|
||||
return _impl.GetEmbeddingControlView(this);
|
||||
});
|
||||
}
|
||||
|
||||
~MediaPlayer()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public DisplayAspectMode AspectMode
|
||||
{
|
||||
get { return (DisplayAspectMode)GetValue(AspectModeProperty); }
|
||||
set { SetValue(AspectModeProperty, value); }
|
||||
}
|
||||
|
||||
public bool AutoPlay
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)GetValue(AutoPlayProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(AutoPlayProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoStop
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)GetValue(AutoStopProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(AutoStopProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLooping
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)GetValue(IsLoopingProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(IsLoopingProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public double BufferingProgress
|
||||
{
|
||||
get
|
||||
{
|
||||
return (double)GetValue(BufferingProgressProperty);
|
||||
}
|
||||
private set
|
||||
{
|
||||
SetValue(BufferingProgressPropertyKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Duration
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)GetValue(DurationProperty);
|
||||
}
|
||||
private set
|
||||
{
|
||||
SetValue(DurationPropertyKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
[Xamarin.Forms.TypeConverter(typeof(MediaSourceConverter))]
|
||||
public MediaSource Source
|
||||
{
|
||||
get { return (MediaSource)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public IVideoOutput VideoOutput
|
||||
{
|
||||
get { return (IVideoOutput)GetValue(VideoOutputProperty); }
|
||||
set { SetValue(VideoOutputProperty, value); }
|
||||
}
|
||||
|
||||
public double Volume
|
||||
{
|
||||
get { return (double)GetValue(VolumeProperty); }
|
||||
set { SetValue(VolumeProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsMuted
|
||||
{
|
||||
get { return (bool)GetValue(IsMutedProperty); }
|
||||
set { SetValue(IsMutedProperty, value); }
|
||||
}
|
||||
|
||||
public int PositionUpdateInterval
|
||||
{
|
||||
get { return (int)GetValue(PositionUpdateIntervalProperty); }
|
||||
set { SetValue(PositionUpdateIntervalProperty, value); }
|
||||
}
|
||||
|
||||
public bool UsesEmbeddingControls
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)GetValue(UsesEmbeddingControlsProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(UsesEmbeddingControlsProperty, value);
|
||||
_impl.UsesEmbeddingControls = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _impl.Position;
|
||||
}
|
||||
private set
|
||||
{
|
||||
SetValue(PositionPropertyKey, value);
|
||||
OnPropertyChanged(nameof(Progress));
|
||||
}
|
||||
}
|
||||
|
||||
public PlaybackState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return (PlaybackState)GetValue(StateProperty);
|
||||
}
|
||||
private set
|
||||
{
|
||||
SetValue(StatePropertyKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBuffering
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)GetValue(IsBufferingProperty);
|
||||
}
|
||||
private set
|
||||
{
|
||||
SetValue(IsBufferingPropertyKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
public double Progress
|
||||
{
|
||||
get
|
||||
{
|
||||
return Position / (double)Math.Max(Position, Duration);
|
||||
}
|
||||
}
|
||||
|
||||
public Command StartCommand => new Command(() =>
|
||||
{
|
||||
if (State == PlaybackState.Playing)
|
||||
{
|
||||
Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
Start();
|
||||
}
|
||||
});
|
||||
|
||||
public Command FastForwardCommand => new Command(() =>
|
||||
{
|
||||
if (State != PlaybackState.Stopped)
|
||||
{
|
||||
Seek(Math.Min(Position + 5000, Duration));
|
||||
}
|
||||
}, () => State != PlaybackState.Stopped);
|
||||
|
||||
public Command RewindCommand => new Command(() =>
|
||||
{
|
||||
if (State != PlaybackState.Stopped)
|
||||
{
|
||||
Seek(Math.Max(Position - 5000, 0));
|
||||
}
|
||||
}, () => State != PlaybackState.Stopped);
|
||||
|
||||
public event EventHandler PlaybackCompleted;
|
||||
public event EventHandler PlaybackStarted;
|
||||
public event EventHandler PlaybackPaused;
|
||||
public event EventHandler PlaybackStopped;
|
||||
public event EventHandler<BufferingProgressUpdatedEventArgs> BufferingProgressUpdated;
|
||||
public event EventHandler BufferingStarted;
|
||||
public event EventHandler BufferingCompleted;
|
||||
public event EventHandler ErrorOccurred;
|
||||
public event EventHandler MediaPrepared;
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
_impl.Pause();
|
||||
}
|
||||
|
||||
public Task<int> Seek(int ms)
|
||||
{
|
||||
ShowController();
|
||||
return _impl.Seek(ms).ContinueWith((t) => Position = _impl.Position);
|
||||
}
|
||||
|
||||
public Task<bool> Start()
|
||||
{
|
||||
return _impl.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_impl.Stop();
|
||||
}
|
||||
|
||||
public Task<Stream> GetAlbumArts()
|
||||
{
|
||||
return _impl.GetAlbumArts();
|
||||
}
|
||||
|
||||
public Task<IDictionary<string, string>> GetMetadata()
|
||||
{
|
||||
return _impl.GetMetadata();
|
||||
}
|
||||
|
||||
public Task<Size> GetVideoSize()
|
||||
{
|
||||
return _impl.GetVideoSize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_impl.UpdateStreamInfo -= OnUpdateStreamInfo;
|
||||
_impl.PlaybackCompleted -= SendPlaybackCompleted;
|
||||
_impl.PlaybackStarted -= SendPlaybackStarted;
|
||||
_impl.PlaybackPaused -= SendPlaybackPaused;
|
||||
_impl.PlaybackStopped -= SendPlaybackStopped;
|
||||
_impl.BufferingProgressUpdated -= OnUpdateBufferingProgress;
|
||||
_impl.ErrorOccurred -= OnErrorOccurred;
|
||||
_impl.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
void UpdateAutoPlay()
|
||||
{
|
||||
_impl.AutoPlay = AutoPlay;
|
||||
}
|
||||
|
||||
void UpdateAutoStop()
|
||||
{
|
||||
_impl.AutoStop = AutoStop;
|
||||
}
|
||||
|
||||
void UpdateIsMuted()
|
||||
{
|
||||
_impl.IsMuted = IsMuted;
|
||||
}
|
||||
|
||||
void UpdateIsLooping()
|
||||
{
|
||||
_impl.IsLooping = IsLooping;
|
||||
}
|
||||
|
||||
void OnUpdateStreamInfo(object sender, EventArgs e)
|
||||
{
|
||||
Duration = _impl.Duration;
|
||||
MediaPrepared?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void SendPlaybackCompleted(object sender, EventArgs e)
|
||||
{
|
||||
PlaybackCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void SendPlaybackStarted(object sender, EventArgs e)
|
||||
{
|
||||
_isPlaying = true;
|
||||
State = PlaybackState.Playing;
|
||||
StartPostionPollingTimer();
|
||||
PlaybackStarted?.Invoke(this, EventArgs.Empty);
|
||||
_controlsAlwaysVisible = false;
|
||||
ShowController();
|
||||
}
|
||||
|
||||
void SendPlaybackPaused(object sender, EventArgs e)
|
||||
{
|
||||
_isPlaying = false;
|
||||
State = PlaybackState.Paused;
|
||||
PlaybackPaused?.Invoke(this, EventArgs.Empty);
|
||||
_controlsAlwaysVisible = true;
|
||||
ShowController();
|
||||
}
|
||||
|
||||
void SendPlaybackStopped(object sender, EventArgs e)
|
||||
{
|
||||
_isPlaying = false;
|
||||
State = PlaybackState.Stopped;
|
||||
Position = 0;
|
||||
PlaybackStopped?.Invoke(this, EventArgs.Empty);
|
||||
_controlsAlwaysVisible = true;
|
||||
ShowController();
|
||||
}
|
||||
|
||||
void StartPostionPollingTimer()
|
||||
{
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(PositionUpdateInterval), () =>
|
||||
{
|
||||
Position = _impl.Position;
|
||||
return _isPlaying;
|
||||
});
|
||||
}
|
||||
|
||||
void OnSourceChanged(object sender, EventArgs e)
|
||||
{
|
||||
_impl.SetSource(Source);
|
||||
}
|
||||
|
||||
void OnVideoOutputChanged()
|
||||
{
|
||||
if (VideoOutput != null)
|
||||
{
|
||||
if (UsesEmbeddingControls)
|
||||
{
|
||||
VideoOutput.Controller = _controls.Value;
|
||||
}
|
||||
VideoOutput.MediaView.Focused += OnVideoOutputFocused;
|
||||
if (VideoOutput.MediaView is View outputView)
|
||||
{
|
||||
TapGestureRecognizer tapGesture = new TapGestureRecognizer();
|
||||
tapGesture.Tapped += OnOutputTapped;
|
||||
outputView.GestureRecognizers.Add(tapGesture);
|
||||
}
|
||||
}
|
||||
_impl.SetDisplay(VideoOutput);
|
||||
}
|
||||
|
||||
void OnOutputTapped(object sender, EventArgs e)
|
||||
{
|
||||
if (!UsesEmbeddingControls)
|
||||
return;
|
||||
if (!_controls.Value.IsVisible)
|
||||
{
|
||||
ShowController();
|
||||
}
|
||||
}
|
||||
|
||||
async void OnUsesEmbeddingControlsChanged()
|
||||
{
|
||||
if (UsesEmbeddingControls)
|
||||
{
|
||||
if (VideoOutput != null)
|
||||
{
|
||||
VideoOutput.Controller = _controls.Value;
|
||||
ShowController();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (VideoOutput != null)
|
||||
{
|
||||
HideController(0);
|
||||
await Task.Delay(200);
|
||||
VideoOutput.Controller = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnVideoOutputFocused(object sender, FocusEventArgs e)
|
||||
{
|
||||
if (UsesEmbeddingControls)
|
||||
{
|
||||
ShowController();
|
||||
}
|
||||
}
|
||||
|
||||
void OnVolumeChanged()
|
||||
{
|
||||
_impl.Volume = Volume;
|
||||
}
|
||||
|
||||
void OnAspectModeChanged()
|
||||
{
|
||||
_impl.AspectMode = AspectMode;
|
||||
}
|
||||
|
||||
void OnUpdateBufferingProgress(object sender, BufferingProgressUpdatedEventArgs e)
|
||||
{
|
||||
if (!IsBuffering && e.Progress >= 0)
|
||||
{
|
||||
IsBuffering = true;
|
||||
BufferingStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else if (IsBuffering && e.Progress == 1.0)
|
||||
{
|
||||
IsBuffering = false;
|
||||
BufferingCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
BufferingProgress = e.Progress;
|
||||
BufferingProgressUpdated?.Invoke(this, new BufferingProgressUpdatedEventArgs { Progress = BufferingProgress });
|
||||
}
|
||||
|
||||
void OnErrorOccurred(object sender, EventArgs e)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
async void HideController(int after)
|
||||
{
|
||||
if (!_controls.IsValueCreated)
|
||||
return;
|
||||
|
||||
_hideTimerCTS?.Cancel();
|
||||
_hideTimerCTS?.Dispose();
|
||||
_hideTimerCTS = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
await Task.Delay(after, _hideTimerCTS.Token);
|
||||
|
||||
if (!_controlsAlwaysVisible)
|
||||
{
|
||||
await _controls.Value.FadeTo(0, 200);
|
||||
_controls.Value.IsVisible = false;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//Exception when canceled
|
||||
}
|
||||
}
|
||||
|
||||
void ShowController()
|
||||
{
|
||||
if (_controls.IsValueCreated)
|
||||
{
|
||||
_controls.Value.IsVisible = true;
|
||||
_controls.Value.FadeTo(1.0, 200);
|
||||
HideController(5000);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnSourceChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as MediaPlayer)?.OnSourceChanged(bindable, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,406 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Tizen.Multimedia;
|
||||
using Xamarin.Forms.PlatformConfiguration.TizenSpecific;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Tizen.Native
|
||||
{
|
||||
public class MediaPlayerImpl : IPlatformMediaPlayer
|
||||
{
|
||||
bool _disposed = false;
|
||||
bool _cancelToStart;
|
||||
DisplayAspectMode _aspectMode = DisplayAspectMode.AspectFit;
|
||||
Player _player;
|
||||
Task _taskPrepare;
|
||||
TaskCompletionSource<bool> _tcsForStreamInfo;
|
||||
IVideoOutput _videoOutput;
|
||||
MediaSource _source;
|
||||
|
||||
public MediaPlayerImpl()
|
||||
{
|
||||
_player = new Player();
|
||||
_player.PlaybackCompleted += OnPlaybackCompleted;
|
||||
_player.BufferingProgressChanged += OnBufferingProgressChanged;
|
||||
_player.ErrorOccurred += OnErrorOccurred;
|
||||
}
|
||||
|
||||
~MediaPlayerImpl()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public bool UsesEmbeddingControls { get; set; }
|
||||
|
||||
public bool AutoPlay { get; set; }
|
||||
|
||||
public bool AutoStop { get; set; }
|
||||
|
||||
public double Volume
|
||||
{
|
||||
get => _player.Volume;
|
||||
set => _player.Volume = (float)value;
|
||||
}
|
||||
|
||||
public bool IsMuted
|
||||
{
|
||||
get => _player.Muted;
|
||||
set => _player.Muted = value;
|
||||
}
|
||||
|
||||
public bool IsLooping
|
||||
{
|
||||
get => _player.IsLooping;
|
||||
set => _player.IsLooping = value;
|
||||
}
|
||||
|
||||
public int Duration => _player.StreamInfo.GetDuration();
|
||||
|
||||
public int Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_player.State == PlayerState.Idle || _player.State == PlayerState.Preparing)
|
||||
return 0;
|
||||
return _player.GetPlayPosition();
|
||||
}
|
||||
}
|
||||
|
||||
public DisplayAspectMode AspectMode
|
||||
{
|
||||
get { return _aspectMode; }
|
||||
set
|
||||
{
|
||||
_aspectMode = value;
|
||||
ApplyAspectMode();
|
||||
}
|
||||
}
|
||||
|
||||
bool HasSource => _source != null;
|
||||
|
||||
IVideoOutput VideoOutput
|
||||
{
|
||||
get { return _videoOutput; }
|
||||
set
|
||||
{
|
||||
if (TargetView != null)
|
||||
TargetView.PropertyChanged -= OnTargetViewPropertyChanged;
|
||||
|
||||
_videoOutput = value;
|
||||
|
||||
if (TargetView != null)
|
||||
{
|
||||
TargetView.PropertyChanged += OnTargetViewPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VisualElement TargetView => VideoOutput?.MediaView;
|
||||
|
||||
Task TaskPrepare
|
||||
{
|
||||
get => _taskPrepare ?? Task.CompletedTask;
|
||||
set => _taskPrepare = value;
|
||||
}
|
||||
|
||||
public event EventHandler UpdateStreamInfo;
|
||||
public event EventHandler PlaybackCompleted;
|
||||
public event EventHandler PlaybackStarted;
|
||||
public event EventHandler<BufferingProgressUpdatedEventArgs> BufferingProgressUpdated;
|
||||
public event EventHandler PlaybackStopped;
|
||||
public event EventHandler PlaybackPaused;
|
||||
public event EventHandler ErrorOccurred;
|
||||
|
||||
public async Task<bool> Start()
|
||||
{
|
||||
_cancelToStart = false;
|
||||
if (!HasSource)
|
||||
return false;
|
||||
|
||||
if (_player.State == PlayerState.Idle)
|
||||
{
|
||||
await Prepare();
|
||||
}
|
||||
|
||||
if (_cancelToStart)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
_player.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error On Start : {e.Message}");
|
||||
return false;
|
||||
}
|
||||
PlaybackStarted?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
try
|
||||
{
|
||||
_player.Pause();
|
||||
PlaybackPaused?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error on Pause : {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_cancelToStart = true;
|
||||
var unusedTask = ChangeToIdleState();
|
||||
PlaybackStopped?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SetDisplay(IVideoOutput output)
|
||||
{
|
||||
VideoOutput = output;
|
||||
}
|
||||
|
||||
public async Task<int> Seek(int ms)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _player.SetPlayPositionAsync(ms, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Fail to seek : {e.Message}");
|
||||
}
|
||||
return Position;
|
||||
}
|
||||
|
||||
public void SetSource(MediaSource source)
|
||||
{
|
||||
_source = source;
|
||||
if (HasSource && AutoPlay)
|
||||
{
|
||||
var unusedTask = Start();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Stream> GetAlbumArts()
|
||||
{
|
||||
if (_player.State == PlayerState.Idle)
|
||||
{
|
||||
if (_tcsForStreamInfo == null || _tcsForStreamInfo.Task.IsCompleted)
|
||||
{
|
||||
_tcsForStreamInfo = new TaskCompletionSource<bool>();
|
||||
}
|
||||
await _tcsForStreamInfo.Task;
|
||||
}
|
||||
await TaskPrepare;
|
||||
|
||||
var imageData = _player.StreamInfo.GetAlbumArt();
|
||||
if (imageData == null)
|
||||
return null;
|
||||
return new MemoryStream(imageData);
|
||||
}
|
||||
|
||||
public async Task<IDictionary<string, string>> GetMetadata()
|
||||
{
|
||||
if (_player.State == PlayerState.Idle)
|
||||
{
|
||||
if (_tcsForStreamInfo == null || _tcsForStreamInfo.Task.IsCompleted)
|
||||
{
|
||||
_tcsForStreamInfo = new TaskCompletionSource<bool>();
|
||||
}
|
||||
await _tcsForStreamInfo.Task;
|
||||
}
|
||||
await TaskPrepare;
|
||||
|
||||
Dictionary<string, string> metadata = new Dictionary<string, string>
|
||||
{
|
||||
[nameof(StreamMetadataKey.Album)] = _player.StreamInfo.GetMetadata(StreamMetadataKey.Album),
|
||||
[nameof(StreamMetadataKey.Artist)] = _player.StreamInfo.GetMetadata(StreamMetadataKey.Artist),
|
||||
[nameof(StreamMetadataKey.Author)] = _player.StreamInfo.GetMetadata(StreamMetadataKey.Author),
|
||||
[nameof(StreamMetadataKey.Genre)] = _player.StreamInfo.GetMetadata(StreamMetadataKey.Genre),
|
||||
[nameof(StreamMetadataKey.Title)] = _player.StreamInfo.GetMetadata(StreamMetadataKey.Title),
|
||||
[nameof(StreamMetadataKey.Year)] = _player.StreamInfo.GetMetadata(StreamMetadataKey.Year)
|
||||
};
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public async Task<Size> GetVideoSize()
|
||||
{
|
||||
if (_player.State == PlayerState.Idle)
|
||||
{
|
||||
if (_tcsForStreamInfo == null || _tcsForStreamInfo.Task.IsCompleted)
|
||||
{
|
||||
_tcsForStreamInfo = new TaskCompletionSource<bool>();
|
||||
}
|
||||
await _tcsForStreamInfo.Task;
|
||||
}
|
||||
await TaskPrepare;
|
||||
|
||||
var videoSize = _player.StreamInfo.GetVideoProperties().Size;
|
||||
return new Size(videoSize.Width, videoSize.Height);
|
||||
}
|
||||
|
||||
public View GetEmbeddingControlView(IMediaPlayer player)
|
||||
{
|
||||
return new EmbeddingControls
|
||||
{
|
||||
BindingContext = player
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_player.PlaybackCompleted -= OnPlaybackCompleted;
|
||||
_player.BufferingProgressChanged -= OnBufferingProgressChanged;
|
||||
_player.ErrorOccurred -= OnErrorOccurred;
|
||||
_player.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
void ApplyDisplay()
|
||||
{
|
||||
if (VideoOutput == null)
|
||||
{
|
||||
_player.Display = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var renderer = Platform.GetRenderer(TargetView);
|
||||
if (renderer is IMediaViewProvider provider && provider.GetMediaView() != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Display display = new Display(provider.GetMediaView());
|
||||
_player.Display = display;
|
||||
_player.DisplaySettings.Mode = _aspectMode.ToNative();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Error("Error on MediaView");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplySource()
|
||||
{
|
||||
if (_source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_source is UriMediaSource uriSource)
|
||||
{
|
||||
var uri = uriSource.Uri;
|
||||
_player.SetSource(new MediaUriSource(uri.IsFile ? uri.LocalPath : uri.AbsoluteUri));
|
||||
}
|
||||
else if (_source is FileMediaSource fileSource)
|
||||
{
|
||||
_player.SetSource(new MediaUriSource(ResourcePath.GetPath(fileSource.File)));
|
||||
}
|
||||
}
|
||||
|
||||
async void OnTargetViewPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "Renderer")
|
||||
{
|
||||
if (Platform.GetRenderer(sender as BindableObject) != null && HasSource && AutoPlay)
|
||||
{
|
||||
await Start();
|
||||
}
|
||||
else if (Platform.GetRenderer(sender as BindableObject) == null && AutoStop)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task Prepare()
|
||||
{
|
||||
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
|
||||
var prevTask = TaskPrepare;
|
||||
TaskPrepare = tcs.Task;
|
||||
await prevTask;
|
||||
|
||||
if (_player.State == PlayerState.Ready)
|
||||
return;
|
||||
|
||||
ApplyDisplay();
|
||||
ApplySource();
|
||||
|
||||
try
|
||||
{
|
||||
await _player.PrepareAsync();
|
||||
UpdateStreamInfo?.Invoke(this, EventArgs.Empty);
|
||||
_tcsForStreamInfo?.TrySetResult(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error on prepare : {e.Message}");
|
||||
}
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
|
||||
async void ApplyAspectMode()
|
||||
{
|
||||
if (_player.State == PlayerState.Preparing)
|
||||
{
|
||||
await TaskPrepare;
|
||||
}
|
||||
_player.DisplaySettings.Mode = AspectMode.ToNative();
|
||||
}
|
||||
|
||||
void OnBufferingProgressChanged(object sender, BufferingProgressChangedEventArgs e)
|
||||
{
|
||||
BufferingProgressUpdated?.Invoke(this, new BufferingProgressUpdatedEventArgs { Progress = e.Percent / 100.0 });
|
||||
}
|
||||
|
||||
void OnPlaybackCompleted(object sender, EventArgs e)
|
||||
{
|
||||
PlaybackCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void OnErrorOccurred(object sender, PlayerErrorOccurredEventArgs e)
|
||||
{
|
||||
Log.Error($"Playback Error Occurred (code:{e.Error})-{e.ToString()}");
|
||||
ErrorOccurred?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
async Task ChangeToIdleState()
|
||||
{
|
||||
switch (_player.State)
|
||||
{
|
||||
case PlayerState.Playing:
|
||||
case PlayerState.Paused:
|
||||
_player.Stop();
|
||||
_player.Unprepare();
|
||||
break;
|
||||
case PlayerState.Ready:
|
||||
_player.Unprepare();
|
||||
break;
|
||||
case PlayerState.Preparing:
|
||||
await TaskPrepare;
|
||||
_player.Unprepare();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Tizen;
|
||||
using MediaPlayerImpl = Xamarin.Forms.Platform.Tizen.Native.MediaPlayerImpl;
|
||||
|
||||
[assembly: Dependency(typeof(ResourcesProvider))]
|
||||
[assembly: Dependency(typeof(Deserializer))]
|
||||
[assembly: Dependency(typeof(NativeBindingService))]
|
||||
[assembly: Dependency(typeof(NativeValueConverterService))]
|
||||
[assembly: Dependency(typeof(MediaPlayerImpl))]
|
||||
|
||||
[assembly: ExportRenderer(typeof(Layout), typeof(LayoutRenderer))]
|
||||
[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))]
|
||||
|
@ -44,7 +42,6 @@ using MediaPlayerImpl = Xamarin.Forms.Platform.Tizen.Native.MediaPlayerImpl;
|
|||
[assembly: ExportRenderer(typeof(CarouselView), typeof(CarouselViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(SwipeView), typeof(SwipeViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(RefreshView), typeof(RefreshViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(MediaElement), typeof(MediaElementRenderer))]
|
||||
[assembly: ExportRenderer(typeof(IndicatorView), typeof(IndicatorViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(RadioButton), typeof(RadioButtonRenderer))]
|
||||
|
||||
|
|
|
@ -1,248 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms.PlatformConfiguration.TizenSpecific;
|
||||
using Xamarin.Forms.Platform.Tizen.Native;
|
||||
using ElmSharp;
|
||||
using Tizen.Multimedia;
|
||||
using XForms = Xamarin.Forms.Forms;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Tizen
|
||||
{
|
||||
public class MediaElementRenderer : ViewRenderer<MediaElement, LayoutCanvas>, IMediaViewProvider, IVideoOutput
|
||||
{
|
||||
MediaPlayer _player;
|
||||
MediaView _mediaView;
|
||||
View _controller;
|
||||
EvasObject _nativeController;
|
||||
|
||||
IMediaElementController Controller => Element as IMediaElementController;
|
||||
|
||||
VisualElement IVideoOutput.MediaView => Element;
|
||||
|
||||
View IVideoOutput.Controller
|
||||
{
|
||||
get
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.Parent = null;
|
||||
Control.Children.Remove(_nativeController);
|
||||
_nativeController.Unrealize();
|
||||
}
|
||||
|
||||
_controller = value;
|
||||
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.Parent = Element;
|
||||
_nativeController = Platform.GetOrCreateRenderer(_controller).NativeView;
|
||||
Control.Children.Add(_nativeController);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoOuputType IVideoOutput.OuputType => VideoOuputType.Buffer;
|
||||
|
||||
MediaView IMediaViewProvider.GetMediaView()
|
||||
{
|
||||
return _mediaView;
|
||||
}
|
||||
|
||||
public MediaElementRenderer()
|
||||
{
|
||||
MediaElement.VerifyMediaElementFlagEnabled(nameof(MediaElementRenderer));
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
|
||||
{
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
e.OldElement.SeekRequested -= OnSeekRequested;
|
||||
e.OldElement.StateRequested -= OnStateRequested;
|
||||
e.OldElement.PositionRequested -= OnPositionRequested;
|
||||
}
|
||||
|
||||
if (Control == null)
|
||||
{
|
||||
SetNativeControl(new LayoutCanvas(XForms.NativeParent));
|
||||
_mediaView = new MediaView(XForms.NativeParent);
|
||||
Control.LayoutUpdated += (s, evt) => OnLayout();
|
||||
Control.Children.Add(_mediaView);
|
||||
Control.AllowFocus(true);
|
||||
|
||||
_player = new MediaPlayer()
|
||||
{
|
||||
VideoOutput = this
|
||||
};
|
||||
_player.PlaybackStarted += OnPlaybackStarted;
|
||||
_player.PlaybackPaused += OnPlaybackPaused;
|
||||
_player.PlaybackStopped += OnPlaybackStopped;
|
||||
_player.PlaybackCompleted += OnPlaybackCompleted;
|
||||
_player.BufferingProgressUpdated += OnBufferingProgressUpdated;
|
||||
_player.ErrorOccurred += OnErrorOccurred;
|
||||
_player.MediaPrepared += OnMediaPrepared;
|
||||
_player.BindingContext = Element;
|
||||
_player.SetBinding(MediaPlayer.SourceProperty, "Source");
|
||||
_player.SetBinding(MediaPlayer.UsesEmbeddingControlsProperty, "ShowsPlaybackControls");
|
||||
_player.SetBinding(MediaPlayer.AutoPlayProperty, "AutoPlay");
|
||||
_player.SetBinding(MediaPlayer.VolumeProperty, "Volume");
|
||||
_player.SetBinding(MediaPlayer.IsLoopingProperty, "IsLooping");
|
||||
_player.SetBinding(MediaPlayer.AspectModeProperty, new Binding
|
||||
{
|
||||
Path = "Aspect",
|
||||
Converter = new AspectToDisplayAspectModeConverter()
|
||||
});
|
||||
BindableObject.SetInheritedBindingContext(_player, Element.BindingContext);
|
||||
|
||||
Element.SeekRequested += OnSeekRequested;
|
||||
Element.StateRequested += OnStateRequested;
|
||||
Element.PositionRequested += OnPositionRequested;
|
||||
}
|
||||
base.OnElementChanged(e);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Element.SeekRequested -= OnSeekRequested;
|
||||
Element.StateRequested -= OnStateRequested;
|
||||
Element.PositionRequested -= OnPositionRequested;
|
||||
_mediaView?.Unrealize();
|
||||
if (_player != null)
|
||||
{
|
||||
_player.PlaybackStarted -= OnPlaybackStarted;
|
||||
_player.PlaybackPaused -= OnPlaybackPaused;
|
||||
_player.PlaybackStopped -= OnPlaybackStopped;
|
||||
_player.PlaybackCompleted -= OnPlaybackCompleted;
|
||||
_player.BufferingProgressUpdated -= OnBufferingProgressUpdated;
|
||||
_player.ErrorOccurred -= OnErrorOccurred;
|
||||
_player.MediaPrepared -= OnMediaPrepared;
|
||||
_player.Dispose();
|
||||
}
|
||||
if (_controller != null )
|
||||
{
|
||||
_controller.Parent = null;
|
||||
Platform.SetRenderer(_controller, null);
|
||||
}
|
||||
_nativeController?.Unrealize();
|
||||
Control?.Unrealize();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected void OnLayout()
|
||||
{
|
||||
_mediaView.Geometry = Control.Geometry;
|
||||
_controller?.Layout(Element.Bounds);
|
||||
if (_nativeController != null)
|
||||
_nativeController.Geometry = Control.Geometry;
|
||||
}
|
||||
|
||||
protected void OnSeekRequested(object sender, SeekRequested e)
|
||||
{
|
||||
_player.Seek((int)e.Position.TotalMilliseconds);
|
||||
}
|
||||
|
||||
protected void OnStateRequested(object sender, StateRequested e)
|
||||
{
|
||||
switch (e.State)
|
||||
{
|
||||
case MediaElementState.Playing:
|
||||
_player.Start();
|
||||
break;
|
||||
case MediaElementState.Paused:
|
||||
_player.Pause();
|
||||
break;
|
||||
case MediaElementState.Stopped:
|
||||
_player.Stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnPositionRequested(object sender, EventArgs e)
|
||||
{
|
||||
Controller.Position = TimeSpan.FromMilliseconds(_player.Position);
|
||||
}
|
||||
|
||||
protected void OnPlaybackStarted(object sender, EventArgs e)
|
||||
{
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
}
|
||||
|
||||
protected void OnPlaybackPaused(object sender, EventArgs e)
|
||||
{
|
||||
Controller.CurrentState = MediaElementState.Paused;
|
||||
}
|
||||
|
||||
protected void OnPlaybackStopped(object sender, EventArgs e)
|
||||
{
|
||||
Controller.CurrentState = MediaElementState.Stopped;
|
||||
}
|
||||
|
||||
protected void OnPlaybackCompleted(object sender, EventArgs e)
|
||||
{
|
||||
Controller.OnMediaEnded();
|
||||
}
|
||||
|
||||
protected void OnBufferingProgressUpdated(object sender, BufferingProgressUpdatedEventArgs e)
|
||||
{
|
||||
if (e.Progress == 1.0)
|
||||
{
|
||||
switch (_player.State)
|
||||
{
|
||||
case PlaybackState.Paused:
|
||||
Controller.CurrentState = MediaElementState.Paused;
|
||||
break;
|
||||
case PlaybackState.Playing:
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
break;
|
||||
case PlaybackState.Stopped:
|
||||
Controller.CurrentState = MediaElementState.Stopped;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Controller.CurrentState != MediaElementState.Buffering && e.Progress >= 0)
|
||||
{
|
||||
Controller.CurrentState = MediaElementState.Buffering;
|
||||
}
|
||||
Controller.BufferingProgress = e.Progress;
|
||||
}
|
||||
|
||||
protected void OnErrorOccurred(object sender, EventArgs e)
|
||||
{
|
||||
Controller.OnMediaFailed();
|
||||
}
|
||||
|
||||
protected async void OnMediaPrepared(object sender, EventArgs e)
|
||||
{
|
||||
Controller.OnMediaOpened();
|
||||
Controller.Duration = TimeSpan.FromMilliseconds(_player.Duration);
|
||||
var videoSize = await _player.GetVideoSize();
|
||||
Controller.VideoWidth = (int)videoSize.Width;
|
||||
Controller.VideoHeight = (int)videoSize.Height;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMediaViewProvider
|
||||
{
|
||||
MediaView GetMediaView();
|
||||
}
|
||||
|
||||
public class AspectToDisplayAspectModeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return ((Aspect)value).ToDisplayAspectMode();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
Двоичные данные
Xamarin.Forms.Platform.Tizen/Resource/img_button_pause.png
Двоичные данные
Xamarin.Forms.Platform.Tizen/Resource/img_button_pause.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 2.6 KiB |
Двоичные данные
Xamarin.Forms.Platform.Tizen/Resource/img_button_play.png
Двоичные данные
Xamarin.Forms.Platform.Tizen/Resource/img_button_play.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 3.0 KiB |
|
@ -102,7 +102,6 @@ namespace Xamarin.Forms.Platform.Tizen
|
|||
Registered.Register(typeof(CarouselView), () => new CarouselViewRenderer());
|
||||
Registered.Register(typeof(SwipeView), () => new SwipeViewRenderer());
|
||||
Registered.Register(typeof(RefreshView), () => new RefreshViewRenderer());
|
||||
Registered.Register(typeof(MediaElement), () => new MediaElementRenderer());
|
||||
Registered.Register(typeof(IndicatorView), () => new IndicatorViewRenderer());
|
||||
Registered.Register(typeof(RadioButton), () => new RadioButtonRenderer());
|
||||
|
||||
|
@ -135,7 +134,6 @@ namespace Xamarin.Forms.Platform.Tizen
|
|||
DependencyService.Register<IDeserializer, Deserializer>();
|
||||
DependencyService.Register<INativeBindingService, NativeBindingService>();
|
||||
DependencyService.Register<INativeValueConverterService, NativeValueConverterService>();
|
||||
DependencyService.Register<IPlatformMediaPlayer, MediaPlayerImpl>();
|
||||
|
||||
//Custom Handlers
|
||||
if (customHandlers != null)
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
<ItemGroup>
|
||||
<None Remove="Resource\arrow_left.png" />
|
||||
<None Remove="Resource\dots_horizontal.png" />
|
||||
<None Remove="Resource\img_button_pause.png" />
|
||||
<None Remove="Resource\img_button_play.png" />
|
||||
<None Remove="Resource\menu.png" />
|
||||
<None Remove="Resource\wc_visual_cue.png" />
|
||||
</ItemGroup>
|
||||
|
@ -21,8 +19,6 @@
|
|||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resource\arrow_left.png" />
|
||||
<EmbeddedResource Include="Resource\dots_horizontal.png" />
|
||||
<EmbeddedResource Include="Resource\img_button_pause.png" />
|
||||
<EmbeddedResource Include="Resource\img_button_play.png" />
|
||||
<EmbeddedResource Include="Resource\menu.png" />
|
||||
<EmbeddedResource Include="Resource\refresh_48dp.png" />
|
||||
<EmbeddedResource Include="Resource\wc_visual_cue.png" />
|
||||
|
|
|
@ -1,305 +0,0 @@
|
|||
using System;
|
||||
using Windows.System.Display;
|
||||
using Windows.UI.Xaml;
|
||||
using Controls = Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Xamarin.Forms.Platform.UWP
|
||||
{
|
||||
public sealed class MediaElementRenderer : ViewRenderer<MediaElement, Controls.MediaElement>
|
||||
{
|
||||
long _bufferingProgressChangedToken;
|
||||
long _positionChangedToken;
|
||||
DisplayRequest _request = new DisplayRequest();
|
||||
|
||||
public MediaElementRenderer()
|
||||
{
|
||||
Xamarin.Forms.MediaElement.VerifyMediaElementFlagEnabled(nameof(MediaElementRenderer));
|
||||
}
|
||||
|
||||
void ReleaseControl()
|
||||
{
|
||||
if (Control is null)
|
||||
return;
|
||||
|
||||
if (_bufferingProgressChangedToken != 0)
|
||||
{
|
||||
Control.UnregisterPropertyChangedCallback(Controls.MediaElement.BufferingProgressProperty, _bufferingProgressChangedToken);
|
||||
_bufferingProgressChangedToken = 0;
|
||||
}
|
||||
|
||||
if (_positionChangedToken != 0)
|
||||
{
|
||||
Control.UnregisterPropertyChangedCallback(Controls.MediaElement.PositionProperty, _positionChangedToken);
|
||||
_positionChangedToken = 0;
|
||||
}
|
||||
|
||||
Element.SeekRequested -= SeekRequested;
|
||||
Element.StateRequested -= StateRequested;
|
||||
Element.PositionRequested -= PositionRequested;
|
||||
|
||||
Control.CurrentStateChanged -= ControlCurrentStateChanged;
|
||||
Control.SeekCompleted -= ControlSeekCompleted;
|
||||
Control.MediaOpened -= ControlMediaOpened;
|
||||
Control.MediaEnded -= ControlMediaEnded;
|
||||
Control.MediaFailed -= ControlMediaFailed;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
ReleaseControl();
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
ReleaseControl();
|
||||
}
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
SetNativeControl(new Controls.MediaElement());
|
||||
Control.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
Control.VerticalAlignment = VerticalAlignment.Stretch;
|
||||
|
||||
Control.AreTransportControlsEnabled = Element.ShowsPlaybackControls;
|
||||
Control.AutoPlay = Element.AutoPlay;
|
||||
Control.IsLooping = Element.IsLooping;
|
||||
Control.Stretch = Element.Aspect.ToStretch();
|
||||
|
||||
_bufferingProgressChangedToken = Control.RegisterPropertyChangedCallback(Controls.MediaElement.BufferingProgressProperty, BufferingProgressChanged);
|
||||
_positionChangedToken = Control.RegisterPropertyChangedCallback(Controls.MediaElement.PositionProperty, PositionChanged);
|
||||
|
||||
Element.SeekRequested += SeekRequested;
|
||||
Element.StateRequested += StateRequested;
|
||||
Element.PositionRequested += PositionRequested;
|
||||
Control.SeekCompleted += ControlSeekCompleted;
|
||||
Control.CurrentStateChanged += ControlCurrentStateChanged;
|
||||
Control.MediaOpened += ControlMediaOpened;
|
||||
Control.MediaEnded += ControlMediaEnded;
|
||||
Control.MediaFailed += ControlMediaFailed;
|
||||
|
||||
UpdateSource();
|
||||
}
|
||||
}
|
||||
|
||||
void PositionRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (!(Control is null))
|
||||
{
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
}
|
||||
|
||||
IMediaElementController Controller => Element as IMediaElementController;
|
||||
|
||||
void StateRequested(object sender, StateRequested e)
|
||||
{
|
||||
if (!(Control is null))
|
||||
{
|
||||
switch (e.State)
|
||||
{
|
||||
case MediaElementState.Playing:
|
||||
Control.Play();
|
||||
break;
|
||||
|
||||
case MediaElementState.Paused:
|
||||
if (Control.CanPause)
|
||||
{
|
||||
Control.Pause();
|
||||
}
|
||||
break;
|
||||
|
||||
case MediaElementState.Stopped:
|
||||
Control.Stop();
|
||||
break;
|
||||
}
|
||||
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
}
|
||||
|
||||
void SeekRequested(object sender, SeekRequested e)
|
||||
{
|
||||
if (!(Control is null) && Control.CanSeek)
|
||||
{
|
||||
Control.Position = e.Position;
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlMediaFailed(object sender, ExceptionRoutedEventArgs e)
|
||||
{
|
||||
Controller?.OnMediaFailed();
|
||||
}
|
||||
|
||||
void ControlMediaEnded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!(Control is null))
|
||||
{
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
|
||||
Controller.CurrentState = MediaElementState.Stopped;
|
||||
Controller.OnMediaEnded();
|
||||
}
|
||||
|
||||
void ControlMediaOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Controller.Duration = Control.NaturalDuration.HasTimeSpan ? Control.NaturalDuration.TimeSpan : (TimeSpan?)null;
|
||||
Controller.VideoHeight = Control.NaturalVideoHeight;
|
||||
Controller.VideoWidth = Control.NaturalVideoWidth;
|
||||
Control.Volume = Element.Volume;
|
||||
Control.Stretch = Element.Aspect.ToStretch();
|
||||
|
||||
Controller.OnMediaOpened();
|
||||
}
|
||||
|
||||
|
||||
void ControlCurrentStateChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Element is null || Control is null)
|
||||
return;
|
||||
|
||||
switch (Control.CurrentState)
|
||||
{
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Playing:
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
_request.RequestActive();
|
||||
}
|
||||
break;
|
||||
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Paused:
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Stopped:
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Closed:
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
_request.RequestRelease();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Controller.CurrentState = FromWindowsMediaElementState(Control.CurrentState);
|
||||
}
|
||||
|
||||
static MediaElementState FromWindowsMediaElementState(Windows.UI.Xaml.Media.MediaElementState state)
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Buffering:
|
||||
return MediaElementState.Buffering;
|
||||
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Closed:
|
||||
return MediaElementState.Closed;
|
||||
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Opening:
|
||||
return MediaElementState.Opening;
|
||||
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Paused:
|
||||
return MediaElementState.Paused;
|
||||
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Playing:
|
||||
return MediaElementState.Playing;
|
||||
|
||||
case Windows.UI.Xaml.Media.MediaElementState.Stopped:
|
||||
return MediaElementState.Stopped;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
void BufferingProgressChanged(DependencyObject sender, DependencyProperty dp)
|
||||
{
|
||||
if (!(Control is null))
|
||||
{
|
||||
Controller.BufferingProgress = Control.BufferingProgress;
|
||||
}
|
||||
}
|
||||
|
||||
void PositionChanged(DependencyObject sender, DependencyProperty dp)
|
||||
{
|
||||
if (!(Control is null))
|
||||
{
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSeekCompleted(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!(Control is null))
|
||||
{
|
||||
Controller.Position = Control.Position;
|
||||
Controller.OnSeekCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(MediaElement.Aspect):
|
||||
Control.Stretch = Element.Aspect.ToStretch();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.AutoPlay):
|
||||
Control.AutoPlay = Element.AutoPlay;
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.IsLooping):
|
||||
Control.IsLooping = Element.IsLooping;
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.KeepScreenOn):
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
if (Control.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Playing)
|
||||
{
|
||||
_request.RequestActive();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_request.RequestRelease();
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.ShowsPlaybackControls):
|
||||
Control.AreTransportControlsEnabled = Element.ShowsPlaybackControls;
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Source):
|
||||
UpdateSource();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Width):
|
||||
Width = Math.Max(0, Element.Width);
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Height):
|
||||
Height = Math.Max(0, Element.Height);
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Volume):
|
||||
Control.Volume = Element.Volume;
|
||||
break;
|
||||
}
|
||||
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
}
|
||||
|
||||
void UpdateSource()
|
||||
{
|
||||
if (Element.Source is null)
|
||||
return;
|
||||
|
||||
if (Element.Source is UriMediaSource uriSource)
|
||||
Control.Source = uriSource.Uri;
|
||||
else if (Element.Source is FileMediaSource fileSource)
|
||||
Control.Source = new Uri(fileSource.File);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,7 +35,6 @@ using Rectangle = Xamarin.Forms.Shapes.Rectangle;
|
|||
[assembly: ExportRenderer(typeof(CheckBox), typeof(CheckBoxRenderer))]
|
||||
[assembly: ExportRenderer(typeof(TableView), typeof(TableViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer))]
|
||||
[assembly: ExportRenderer(typeof(MediaElement), typeof(MediaElementRenderer))]
|
||||
[assembly: ExportRenderer(typeof(RefreshView), typeof(RefreshViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(Shell), typeof(ShellRenderer))]
|
||||
[assembly: ExportRenderer(typeof(Path), typeof(PathRenderer))]
|
||||
|
|
|
@ -33,7 +33,6 @@ using Rectangle = Xamarin.Forms.Shapes.Rectangle;
|
|||
[assembly: ExportRenderer(typeof(Frame), typeof(FrameRenderer))]
|
||||
[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(OpenGLView), typeof(OpenGLViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(MediaElement), typeof(MediaElementRenderer))]
|
||||
[assembly: ExportRenderer(typeof(ImageButton), typeof(ImageButtonRenderer))]
|
||||
[assembly: ExportRenderer(typeof(EmbeddedFont), typeof(EmbeddedFontLoader))]
|
||||
[assembly: ExportRenderer(typeof(Path), typeof(PathRenderer))]
|
||||
|
|
|
@ -1,308 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Xamarin.Forms.Platform.WPF
|
||||
{
|
||||
public sealed class MediaElementRenderer : ViewRenderer<MediaElement, System.Windows.Controls.MediaElement>
|
||||
{
|
||||
IMediaElementController Controller => Element as IMediaElementController;
|
||||
MediaElementState _requestedState;
|
||||
|
||||
public MediaElementRenderer()
|
||||
{
|
||||
Xamarin.Forms.MediaElement.VerifyMediaElementFlagEnabled(nameof(MediaElementRenderer));
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
Element.SeekRequested -= ElementSeekRequested;
|
||||
Element.StateRequested -= ElementStateRequested;
|
||||
Element.PositionRequested -= ElementPositionRequested;
|
||||
|
||||
if (Control != null)
|
||||
{
|
||||
Control.BufferingStarted -= ControlBufferingStarted;
|
||||
Control.BufferingEnded -= ControlBufferingEnded;
|
||||
Control.MediaOpened -= ControlMediaOpened;
|
||||
Control.MediaEnded -= ControlMediaEnded;
|
||||
Control.MediaFailed -= ControlMediaFailed;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
SetNativeControl(new System.Windows.Controls.MediaElement());
|
||||
Control.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
Control.VerticalAlignment = VerticalAlignment.Stretch;
|
||||
|
||||
Control.LoadedBehavior = MediaState.Manual;
|
||||
Control.UnloadedBehavior = MediaState.Close;
|
||||
Control.Stretch = Element.Aspect.ToStretch();
|
||||
|
||||
Control.BufferingStarted += ControlBufferingStarted;
|
||||
Control.BufferingEnded += ControlBufferingEnded;
|
||||
Control.MediaOpened += ControlMediaOpened;
|
||||
Control.MediaEnded += ControlMediaEnded;
|
||||
Control.MediaFailed += ControlMediaFailed;
|
||||
|
||||
Element.SeekRequested += ElementSeekRequested;
|
||||
Element.StateRequested += ElementStateRequested;
|
||||
Element.PositionRequested += ElementPositionRequested;
|
||||
UpdateSource();
|
||||
}
|
||||
}
|
||||
|
||||
void ElementPositionRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (!(Control is null))
|
||||
{
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
}
|
||||
|
||||
void ElementStateRequested(object sender, StateRequested e)
|
||||
{
|
||||
_requestedState = e.State;
|
||||
|
||||
switch (e.State)
|
||||
{
|
||||
case MediaElementState.Playing:
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
DisplayRequestActive();
|
||||
}
|
||||
|
||||
Control.Play();
|
||||
Controller.CurrentState = _requestedState;
|
||||
break;
|
||||
|
||||
case MediaElementState.Paused:
|
||||
if (Control.CanPause)
|
||||
{
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
DisplayRequestRelease();
|
||||
}
|
||||
|
||||
Control.Pause();
|
||||
Controller.CurrentState = _requestedState;
|
||||
}
|
||||
break;
|
||||
|
||||
case MediaElementState.Stopped:
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
DisplayRequestRelease();
|
||||
}
|
||||
|
||||
Control.Stop();
|
||||
Controller.CurrentState = _requestedState;
|
||||
break;
|
||||
}
|
||||
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
|
||||
void ElementSeekRequested(object sender, SeekRequested e)
|
||||
{
|
||||
Control.Position = e.Position;
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
|
||||
void UpdateSource()
|
||||
{
|
||||
if (Element.Source is null)
|
||||
return;
|
||||
|
||||
if (Control.Clock != null)
|
||||
Control.Clock = null;
|
||||
|
||||
if (Element.Source is UriMediaSource uriSource)
|
||||
{
|
||||
if (uriSource.Uri.Scheme == "ms-appx")
|
||||
{
|
||||
Control.Source = new Uri(Element.Source.ToString().Replace("ms-appx:///", ""), UriKind.Relative);
|
||||
}
|
||||
else if (uriSource.Uri.Scheme == "ms-appdata")
|
||||
{
|
||||
string filePath = string.Empty;
|
||||
|
||||
if (uriSource.Uri.LocalPath.StartsWith("/local"))
|
||||
{
|
||||
// WPF doesn't have the concept of an app package local folder so using My Documents as a placeholder
|
||||
filePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), uriSource.Uri.LocalPath.Substring(7));
|
||||
}
|
||||
else if (uriSource.Uri.LocalPath.StartsWith("/temp"))
|
||||
{
|
||||
filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), uriSource.Uri.LocalPath.Substring(6));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid Uri", "Source");
|
||||
}
|
||||
|
||||
Control.Source = new Uri(filePath);
|
||||
}
|
||||
else if (uriSource.Uri.Scheme == "https")
|
||||
{
|
||||
throw new ArgumentException("WPF supports only HTTP remote sources and not the HTTPS URI scheme.", "Source");
|
||||
}
|
||||
else
|
||||
{
|
||||
Control.Source = uriSource.Uri;
|
||||
}
|
||||
}
|
||||
else if (Element.Source is FileMediaSource fileSource)
|
||||
{
|
||||
Control.Source = new Uri(fileSource.File);
|
||||
}
|
||||
|
||||
Controller.CurrentState = MediaElementState.Opening;
|
||||
}
|
||||
|
||||
void ControlBufferingEnded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Controller.BufferingProgress = 1.0;
|
||||
if (Element.AutoPlay)
|
||||
{
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
}
|
||||
else
|
||||
{
|
||||
Controller.CurrentState = _requestedState; ;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlBufferingStarted(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Controller.BufferingProgress = 0.0;
|
||||
Controller.CurrentState = MediaElementState.Buffering;
|
||||
}
|
||||
|
||||
void ControlMediaFailed(object sender, ExceptionRoutedEventArgs e)
|
||||
{
|
||||
Controller.OnMediaFailed();
|
||||
}
|
||||
|
||||
void ControlMediaEnded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Element.IsLooping)
|
||||
{
|
||||
Control.Position = TimeSpan.Zero;
|
||||
Control.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
_requestedState = MediaElementState.Stopped;
|
||||
Controller.CurrentState = MediaElementState.Stopped;
|
||||
Controller.OnMediaEnded();
|
||||
}
|
||||
|
||||
Controller.Position = Control.Position;
|
||||
}
|
||||
|
||||
void ControlMediaOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Controller.Duration = Control.NaturalDuration.HasTimeSpan ? Control.NaturalDuration.TimeSpan : (TimeSpan?)null;
|
||||
Controller.VideoHeight = Control.NaturalVideoHeight;
|
||||
Controller.VideoWidth = Control.NaturalVideoWidth;
|
||||
Controller.OnMediaOpened();
|
||||
|
||||
if (Element.AutoPlay)
|
||||
{
|
||||
Control.Play();
|
||||
_requestedState = MediaElementState.Playing;
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
}
|
||||
else
|
||||
{
|
||||
Controller.CurrentState = _requestedState;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSeekCompleted(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Controller.Position = Control.Position;
|
||||
Controller.OnSeekCompleted();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(MediaElement.Aspect):
|
||||
Control.Stretch = Element.Aspect.ToStretch();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.KeepScreenOn):
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
if (Element.CurrentState == MediaElementState.Playing)
|
||||
{
|
||||
DisplayRequestActive();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayRequestRelease();
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Source):
|
||||
UpdateSource();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Volume):
|
||||
Control.Volume = Element.Volume;
|
||||
break;
|
||||
}
|
||||
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
}
|
||||
|
||||
protected override void UpdateWidth()
|
||||
{
|
||||
Control.Width = Math.Max(0, Element.Width);
|
||||
}
|
||||
|
||||
protected override void UpdateHeight()
|
||||
{
|
||||
Control.Height = Math.Max(0, Element.Height);
|
||||
}
|
||||
|
||||
void DisplayRequestActive()
|
||||
{
|
||||
NativeMethods.SetThreadExecutionState(NativeMethods.EXECUTION_STATE.DISPLAY_REQUIRED | NativeMethods.EXECUTION_STATE.CONTINUOUS);
|
||||
}
|
||||
|
||||
void DisplayRequestRelease()
|
||||
{
|
||||
NativeMethods.SetThreadExecutionState(NativeMethods.EXECUTION_STATE.CONTINUOUS);
|
||||
}
|
||||
|
||||
static class NativeMethods
|
||||
{
|
||||
[DllImport("Kernel32", SetLastError = true)]
|
||||
internal static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
|
||||
|
||||
internal enum EXECUTION_STATE : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Informs the system that the state being set should remain in effect until the next call that uses ES_CONTINUOUS and one of the other state flags is cleared.
|
||||
/// </summary>
|
||||
CONTINUOUS = 0x80000000,
|
||||
|
||||
/// <summary>
|
||||
/// Forces the display to be on by resetting the display idle timer.
|
||||
/// </summary>
|
||||
DISPLAY_REQUIRED = 0x00000002,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,451 +0,0 @@
|
|||
using AVFoundation;
|
||||
using AVKit;
|
||||
using CoreGraphics;
|
||||
using CoreMedia;
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.IO;
|
||||
using UIKit;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.iOS
|
||||
{
|
||||
public sealed class MediaElementRenderer : ViewRenderer<MediaElement, UIView>
|
||||
{
|
||||
IMediaElementController Controller => Element as IMediaElementController;
|
||||
|
||||
AVPlayerViewController _avPlayerViewController = new AVPlayerViewController();
|
||||
NSObject _playedToEndObserver;
|
||||
NSObject _statusObserver;
|
||||
NSObject _rateObserver;
|
||||
|
||||
bool _idleTimerDisabled = false;
|
||||
|
||||
[Internals.Preserve(Conditional = true)]
|
||||
public MediaElementRenderer()
|
||||
{
|
||||
MediaElement.VerifyMediaElementFlagEnabled(nameof(MediaElementRenderer));
|
||||
}
|
||||
|
||||
void SetKeepScreenOn(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (!UIApplication.SharedApplication.IdleTimerDisabled)
|
||||
{
|
||||
_idleTimerDisabled = true;
|
||||
UIApplication.SharedApplication.IdleTimerDisabled = true;
|
||||
}
|
||||
}
|
||||
else if (_idleTimerDisabled)
|
||||
{
|
||||
_idleTimerDisabled = false;
|
||||
UIApplication.SharedApplication.IdleTimerDisabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSource()
|
||||
{
|
||||
if (Element.Source != null)
|
||||
{
|
||||
AVAsset asset = null;
|
||||
|
||||
var uriSource = Element.Source as UriMediaSource;
|
||||
if (uriSource != null)
|
||||
{
|
||||
if (uriSource.Uri.Scheme == "ms-appx")
|
||||
{
|
||||
if (uriSource.Uri.LocalPath.Length <= 1)
|
||||
return;
|
||||
|
||||
// used for a file embedded in the application package
|
||||
asset = AVAsset.FromUrl(NSUrl.FromFilename(uriSource.Uri.LocalPath.Substring(1)));
|
||||
}
|
||||
else if (uriSource.Uri.Scheme == "ms-appdata")
|
||||
{
|
||||
string filePath = Platform.ResolveMsAppDataUri(uriSource.Uri);
|
||||
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
throw new ArgumentException("Invalid Uri", "Source");
|
||||
|
||||
asset = AVAsset.FromUrl(NSUrl.FromFilename(filePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
asset = AVUrlAsset.Create(NSUrl.FromString(uriSource.Uri.AbsoluteUri));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fileSource = Element.Source as FileMediaSource;
|
||||
if (fileSource != null)
|
||||
{
|
||||
asset = AVAsset.FromUrl(NSUrl.FromFilename(fileSource.File));
|
||||
}
|
||||
}
|
||||
|
||||
var item = new AVPlayerItem(asset);
|
||||
RemoveStatusObserver();
|
||||
|
||||
_statusObserver = (NSObject)item.AddObserver("status", NSKeyValueObservingOptions.New, ObserveStatus);
|
||||
|
||||
|
||||
if (_avPlayerViewController.Player != null)
|
||||
{
|
||||
_avPlayerViewController.Player.ReplaceCurrentItemWithPlayerItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
_avPlayerViewController.Player = new AVPlayer(item);
|
||||
_rateObserver = (NSObject)_avPlayerViewController.Player.AddObserver("rate", NSKeyValueObservingOptions.New, ObserveRate);
|
||||
}
|
||||
|
||||
if (Element.AutoPlay)
|
||||
Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Element.CurrentState == MediaElementState.Playing || Element.CurrentState == MediaElementState.Buffering)
|
||||
{
|
||||
_avPlayerViewController.Player.Pause();
|
||||
Controller.CurrentState = MediaElementState.Stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(_playedToEndObserver != null)
|
||||
{
|
||||
NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
|
||||
_playedToEndObserver = null;
|
||||
}
|
||||
|
||||
if(_rateObserver != null)
|
||||
{
|
||||
_rateObserver.Dispose();
|
||||
_rateObserver = null;
|
||||
}
|
||||
|
||||
RemoveStatusObserver();
|
||||
|
||||
_avPlayerViewController?.Player?.Pause();
|
||||
_avPlayerViewController?.Player?.ReplaceCurrentItemWithPlayerItem(null);
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
void RemoveStatusObserver()
|
||||
{
|
||||
if (_statusObserver != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_avPlayerViewController?.Player?.CurrentItem?.RemoveObserver(_statusObserver, "status");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
_statusObserver = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ObserveRate(NSObservedChange e)
|
||||
{
|
||||
if (Controller is object)
|
||||
{
|
||||
switch (_avPlayerViewController.Player.Rate)
|
||||
{
|
||||
case 0.0f:
|
||||
Controller.CurrentState = MediaElementState.Paused;
|
||||
break;
|
||||
|
||||
case 1.0f:
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
break;
|
||||
}
|
||||
|
||||
Controller.Position = Position;
|
||||
}
|
||||
}
|
||||
|
||||
void ObserveStatus(NSObservedChange e)
|
||||
{
|
||||
Controller.Volume = _avPlayerViewController.Player.Volume;
|
||||
|
||||
switch (_avPlayerViewController.Player.Status)
|
||||
{
|
||||
case AVPlayerStatus.Failed:
|
||||
Controller.OnMediaFailed();
|
||||
break;
|
||||
|
||||
case AVPlayerStatus.ReadyToPlay:
|
||||
var duration = _avPlayerViewController.Player.CurrentItem.Duration;
|
||||
|
||||
if (duration.IsIndefinite)
|
||||
Controller.Duration = TimeSpan.Zero;
|
||||
else
|
||||
Controller.Duration = TimeSpan.FromSeconds(duration.Seconds);
|
||||
|
||||
Controller.VideoHeight = (int)_avPlayerViewController.Player.CurrentItem.Asset.NaturalSize.Height;
|
||||
Controller.VideoWidth = (int)_avPlayerViewController.Player.CurrentItem.Asset.NaturalSize.Width;
|
||||
Controller.OnMediaOpened();
|
||||
Controller.Position = Position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TimeSpan Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_avPlayerViewController.Player.CurrentTime.IsInvalid)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
return TimeSpan.FromSeconds(_avPlayerViewController.Player.CurrentTime.Seconds);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayedToEnd(NSNotification notification)
|
||||
{
|
||||
if (Element == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Element.IsLooping)
|
||||
{
|
||||
_avPlayerViewController.Player.Seek(CMTime.Zero);
|
||||
Controller.Position = Position;
|
||||
_avPlayerViewController.Player.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetKeepScreenOn(false);
|
||||
Controller.Position = Position;
|
||||
|
||||
try
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(Controller.OnMediaEnded);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warning("MediaElement", $"Failed to play media to end: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(MediaElement.Aspect):
|
||||
_avPlayerViewController.VideoGravity = AspectToGravity(Element.Aspect);
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.KeepScreenOn):
|
||||
if (!Element.KeepScreenOn)
|
||||
{
|
||||
SetKeepScreenOn(false);
|
||||
}
|
||||
else if(Element.CurrentState == MediaElementState.Playing)
|
||||
{
|
||||
// only toggle this on if property is set while video is already running
|
||||
SetKeepScreenOn(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.ShowsPlaybackControls):
|
||||
_avPlayerViewController.ShowsPlaybackControls = Element.ShowsPlaybackControls;
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Source):
|
||||
UpdateSource();
|
||||
break;
|
||||
|
||||
case nameof(MediaElement.Volume):
|
||||
_avPlayerViewController.Player.Volume = (float)Element.Volume;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MediaElementSeekRequested(object sender, SeekRequested e)
|
||||
{
|
||||
if (_avPlayerViewController.Player.Status != AVPlayerStatus.ReadyToPlay || _avPlayerViewController.Player.CurrentItem == null)
|
||||
return;
|
||||
|
||||
NSValue[] ranges = _avPlayerViewController.Player.CurrentItem.SeekableTimeRanges;
|
||||
CMTime seekTo = new CMTime(Convert.ToInt64(e.Position.TotalMilliseconds), 1000);
|
||||
foreach (NSValue v in ranges)
|
||||
{
|
||||
if (seekTo >= v.CMTimeRangeValue.Start && seekTo < (v.CMTimeRangeValue.Start + v.CMTimeRangeValue.Duration))
|
||||
{
|
||||
_avPlayerViewController.Player.Seek(seekTo, SeekComplete);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Play()
|
||||
{
|
||||
var audioSession = AVAudioSession.SharedInstance();
|
||||
NSError err = audioSession.SetCategory(AVAudioSession.CategoryPlayback);
|
||||
if (!(err is null))
|
||||
Log.Warning("MediaElement", "Failed to set AVAudioSession Category {0}", err.Code);
|
||||
|
||||
audioSession.SetMode(AVAudioSession.ModeMoviePlayback, out err);
|
||||
if (!(err is null))
|
||||
Log.Warning("MediaElement", "Failed to set AVAudioSession Mode {0}", err.Code);
|
||||
|
||||
err = audioSession.SetActive(true);
|
||||
if (!(err is null))
|
||||
Log.Warning("MediaElement", "Failed to set AVAudioSession Active {0}", err.Code);
|
||||
|
||||
if (_avPlayerViewController.Player != null)
|
||||
{
|
||||
_avPlayerViewController.Player.Play();
|
||||
Controller.CurrentState = MediaElementState.Playing;
|
||||
}
|
||||
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
SetKeepScreenOn(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaElementStateRequested(object sender, StateRequested e)
|
||||
{
|
||||
MediaElementVolumeRequested(this, EventArgs.Empty);
|
||||
|
||||
switch (e.State)
|
||||
{
|
||||
case MediaElementState.Playing:
|
||||
Play();
|
||||
break;
|
||||
|
||||
case MediaElementState.Paused:
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
SetKeepScreenOn(false);
|
||||
}
|
||||
|
||||
if (_avPlayerViewController.Player != null)
|
||||
{
|
||||
_avPlayerViewController.Player.Pause();
|
||||
Controller.CurrentState = MediaElementState.Paused;
|
||||
}
|
||||
break;
|
||||
|
||||
case MediaElementState.Stopped:
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
SetKeepScreenOn(false);
|
||||
}
|
||||
//ios has no stop...
|
||||
_avPlayerViewController?.Player.Pause();
|
||||
_avPlayerViewController?.Player.Seek(CMTime.Zero);
|
||||
Controller.CurrentState = MediaElementState.Stopped;
|
||||
|
||||
NSError err = AVAudioSession.SharedInstance().SetActive(false);
|
||||
if (!(err is null))
|
||||
Log.Warning("MediaElement", "Failed to set AVAudioSession Inactive {0}", err.Code);
|
||||
break;
|
||||
}
|
||||
|
||||
Controller.Position = Position;
|
||||
}
|
||||
|
||||
static AVLayerVideoGravity AspectToGravity(Aspect aspect)
|
||||
{
|
||||
switch (aspect)
|
||||
{
|
||||
case Aspect.Fill:
|
||||
return AVLayerVideoGravity.Resize;
|
||||
|
||||
case Aspect.AspectFill:
|
||||
return AVLayerVideoGravity.ResizeAspectFill;
|
||||
|
||||
default:
|
||||
return AVLayerVideoGravity.ResizeAspect;
|
||||
}
|
||||
}
|
||||
|
||||
void SeekComplete(bool finished)
|
||||
{
|
||||
if (finished)
|
||||
{
|
||||
Controller.OnSeekCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
private void MediaElementVolumeRequested(object sender, EventArgs e)
|
||||
{
|
||||
Controller.Volume = _avPlayerViewController.Player.Volume;
|
||||
}
|
||||
|
||||
void MediaElementPositionRequested(object sender, EventArgs e)
|
||||
{
|
||||
Controller.Position = Position;
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
|
||||
e.OldElement.SeekRequested -= MediaElementSeekRequested;
|
||||
e.OldElement.StateRequested -= MediaElementStateRequested;
|
||||
e.OldElement.PositionRequested -= MediaElementPositionRequested;
|
||||
e.OldElement.VolumeRequested -= MediaElementVolumeRequested;
|
||||
|
||||
if (_playedToEndObserver != null)
|
||||
{
|
||||
NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
|
||||
_playedToEndObserver = null;
|
||||
}
|
||||
|
||||
// stop video if playing
|
||||
if (_avPlayerViewController?.Player?.CurrentItem != null)
|
||||
{
|
||||
RemoveStatusObserver();
|
||||
|
||||
_avPlayerViewController.Player.Pause();
|
||||
_avPlayerViewController.Player.Seek(CMTime.Zero);
|
||||
_avPlayerViewController.Player.ReplaceCurrentItemWithPlayerItem(null);
|
||||
AVAudioSession.SharedInstance().SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
SetNativeControl(_avPlayerViewController.View);
|
||||
|
||||
Element.PropertyChanged += OnElementPropertyChanged;
|
||||
Element.SeekRequested += MediaElementSeekRequested;
|
||||
Element.StateRequested += MediaElementStateRequested;
|
||||
Element.PositionRequested += MediaElementPositionRequested;
|
||||
Element.VolumeRequested += MediaElementVolumeRequested;
|
||||
|
||||
_avPlayerViewController.ShowsPlaybackControls = Element.ShowsPlaybackControls;
|
||||
_avPlayerViewController.VideoGravity = AspectToGravity(Element.Aspect);
|
||||
if (Element.KeepScreenOn)
|
||||
{
|
||||
SetKeepScreenOn(true);
|
||||
}
|
||||
|
||||
_playedToEndObserver = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTimeNotification, PlayedToEnd);
|
||||
|
||||
UpdateBackgroundColor();
|
||||
UpdateSource();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateBackgroundColor()
|
||||
{
|
||||
BackgroundColor = Element.BackgroundColor.ToUIColor();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -233,7 +233,6 @@
|
|||
<Compile Include="Renderers\ButtonElementManager.cs" />
|
||||
<Compile Include="Renderers\IImageVisualElementRenderer.cs" />
|
||||
<Compile Include="Renderers\ImageButtonRenderer.cs" />
|
||||
<Compile Include="Renderers\MediaElementRenderer.cs" />
|
||||
<Compile Include="Resources\StringResources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
|
|
|
@ -139,11 +139,7 @@
|
|||
{
|
||||
}
|
||||
|
||||
internal class _MediaElementRenderer
|
||||
{
|
||||
}
|
||||
|
||||
internal class _SwipeViewRenderer
|
||||
internal class _SwipeViewRenderer
|
||||
{
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче