追記
Windows10 Anniversary Update 以後では MediaPlayer 及び MediaPlayerElement を利用することで、メディアトランスポートやバックグラウンド再生が簡単にサポートできるようになりました。
動画ストリームをMediaSourceとして扱う必要がありますが、MediaElementで動画を扱うよりもコントロールが容易になります。
後方互換性が要件が必須な場合を除いては、MediaPlayer/MediaPlayerElementの利用をオススメします。
はじめに
ここで扱うのは Windows.UI.Xaml.Controls.MediaElement です。
System.Windows.Controls.MediaElement ではないのでご注意ください。
MediaElement.Positionプロパティについて
MediaElementの再生位置はPositionプロパティを通じて行いますが、MediaElement上で再生を開始してもPositionプロパティへの操作は内部からは行われません。そのためPositionプロパティのプロパティ変更通知もトリガーされません。
Positionプロパティについて
https://msdn.microsoft.com/ja-jp/library/system.windows.controls.mediaelement.position(v=vs.110).aspx
このページによると、Clockプロパティが設定されてない時にPositionが設定されたとき再生位置を指定できる。とありますが、読み取りについては記述がありません。
また、Positionプロパティの変更を伝えるイベントも存在しないようです。
そうなると再生に合わせてPositionプロパティの値を取得するには、Timer等でPositionの内容をモニターして定期的に取り出す処理が必要になります。
MediaElementの再生状況を知る方法
基本的にはMediaElement.CurrentStateプロパティまたは、CurrentStateChangedイベントを通じて現在の再生状況を把握することが出来ます。
TimerでPositionプロパティをモニターする際、メディアのクローズ後やポーズ中にモニター処理が走らないように工夫できると良さそうですね。
Positionプロパティを定期読み取りするビヘイビア
using System;
using System.Threading;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Microsoft.Xaml.Interactivity;
namespace MyApp.Views.Behaviors
{
public class MediaElementExtractPositionBehavior : Behavior<MediaElement>
{
#region Position Property
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position"
, typeof(TimeSpan)
, typeof(MediaElementExtractPositionBehavior)
, new PropertyMetadata(default(TimeSpan))
);
public TimeSpan Position
{
get { return (TimeSpan)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
#endregion
#region Interval Property
public static readonly DependencyProperty IntervalProperty =
DependencyProperty.Register("Interval"
, typeof(TimeSpan)
, typeof(MediaElementExtractPositionBehavior)
, new PropertyMetadata(default(TimeSpan))
);
public TimeSpan Interval
{
get { return (TimeSpan)GetValue(IntervalProperty); }
set { SetValue(IntervalProperty, value); }
}
#endregion
#region MediaElement Event Handling
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
this.AssociatedObject.CurrentStateChanged += AssociatedObject_CurrentStateChanged;
this.AssociatedObject.SeekCompleted += AssociatedObject_SeekCompleted;
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
this.AssociatedObject.Unloaded += AssociatedObject_Unloaded;
}
private void AssociatedObject_SeekCompleted(object sender, RoutedEventArgs e)
{
this.ExtractPosition();
}
private void AssociatedObject_CurrentStateChanged(object sender, RoutedEventArgs e)
{
var mediaElem = (MediaElement)sender;
switch (mediaElem.CurrentState)
{
case Windows.UI.Xaml.Media.MediaElementState.Closed:
TimerExit();
break;
case Windows.UI.Xaml.Media.MediaElementState.Opening:
RefreshTimerSetting();
break;
case Windows.UI.Xaml.Media.MediaElementState.Buffering:
break;
case Windows.UI.Xaml.Media.MediaElementState.Playing:
RefreshTimerSetting();
break;
case Windows.UI.Xaml.Media.MediaElementState.Paused:
TimerExit();
break;
case Windows.UI.Xaml.Media.MediaElementState.Stopped:
TimerExit();
break;
default:
break;
}
}
private void AssociatedObject_Unloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
TimerExit();
}
protected override void OnDetaching()
{
base.OnDetaching();
TimerExit();
}
#endregion
#region Management Timer
private Timer _ExtractTimingTimer;
private void RefreshTimerSetting()
{
TimerExit();
_ExtractTimingTimer = new Timer((x) =>
{
var me = (MediaElementExtractPositionBehavior)x;
me.ExtractPosition();
}, this, 0, (int)Interval.TotalMilliseconds);
}
private void TimerExit()
{
_ExtractTimingTimer?.Dispose();
_ExtractTimingTimer = null;
}
private async void ExtractPosition()
{
var mediaElem = this.AssociatedObject as MediaElement;
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low,
() => this.Position = mediaElem.Position);
}
#endregion
}
}
気軽にTimerスレッドを作ったり消したりしていますが、気になる場合にはCurrentStateのOpeningとClosedでだけ作成と削除を行えば問題ないと思います。
SeekCompletedイベントにも反応するようにしていますが、シーク後にもPlayingステートへの変更が呼ばれると思う(動作未確認)ので不要かもしれません。
Xamlで利用する方法は以下の通り
<Page
x:Class="MyApp.Views.VideoPlayerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:mybehaviors="MyApp.Views.Behaviors"
mc:Ignorable="d">
<Grid>
<MediaElement Source="smile.mp4">
<i:Interaction.Behaviors>
<mybehaviors:MediaElementExtractPositionBehavior
Position="{Binding CurrentVideoPosition, Mode=TwoWay}"
Interval="00:00:00.016"
/>
</i:Interaction.Behaviors>
</MediaElement>
</Grid>
</Page>
PositionにBindingしているCurrentVideoPositionは、VideoPlayerPageのDataContextに設定するVideoPlayerPageViewModel上で定義します。
IntervalにはTimeSpan型の固定値を渡しています。この例では0.016秒(約1/60秒)ごとにPositionプロパティを読み込むことになります。
他によくわからないところはコメントもらえれば補足します。
参考
MediaElement
https://msdn.microsoft.com/ja-jp/library/windows/apps/windows.ui.xaml.controls.mediaelement