WPF 超入門 〜番外編「とある動画の同期再生」


先日、MSDN のフォーラム

1つの Form に4つの MediaPlayer のコントロールを貼り付けている。1つの動画を同時再生させたいが微妙に再生がずれてしまう。4つの MediaPlayer コントロールで同期を取って再生する方法はないか?」(取意)

という質問がありました。

ここ最近エッセンシャルWPF 読んでたせいで、この質問見て真っ先に思い浮かんだのが MediaElementVisualBrush を使う方法です。
この VisualBrush というのはかなり強力で、対象となるコントロールに描画されたグラフィックをそのまま描画するという代物。しかも静止画に留まらず、動画も同期を取って再生しちゃいます。
例えばこんな具合。左上が MediaElement コントロールで、残り三つは Rectangle の背景を VisualBrush を使って塗り潰してます。


では実例で見てみましょう。


サンプルプロジェクト

VisualStudio 2010 を起動し、C#WPF アプリケーションのプロジェクトを作成します。プロジェクト名は「VisualBrushSample」としました。まぁベタな名前ですが良しとしましょう。


まず MediaElement と Button のみ。以下 XAML の定義です。Button は「Play(再生)」ボタンと「Pause(一時停止)」ボタン、「Stop(停止)」ボタンを用意しました。ここはメンドくさい説明を省きます。MainWindow.xaml に以下の XAML をコピペしてください。

MainWindow.xaml

<Window x:Class="VisualBrushSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="MainWindow" Height="380" Width="700">
    <Grid>
        <MediaElement Height="160" Width="280" HorizontalAlignment="Left" Name="MediaElement1" 
           VerticalAlignment="Top" LoadedBehavior="Manual" UnloadedBehavior="Manual" Stretch="Fill" />
        <Button Content="Play" Height="30" 
                HorizontalAlignment="Left" Margin="580,11,0,0" 
                Name="Button1" VerticalAlignment="Top" Width="90" Click="Button1_Click" />
        <Button Content="Pause" Height="30" 
                HorizontalAlignment="Left" Margin="580,55,0,0" 
                Name="Button2" VerticalAlignment="Top" Width="90" Click="Button2_Click" />
        <Button Content="Stop" Height="30" 
                HorizontalAlignment="Left" Margin="580,103,0,0" 
                Name="Button3" VerticalAlignment="Top" Width="90" Click="Button3_Click" />
    </Grid>
</Window>

次は C# のコードです。MainWindow.xaml.cs に以下のコードを上書きします。
MediaElement1.Source に設定してる動画のファイル名がかなり怪しいですが、まぁここは気にしないことにしましょうw

using System.Windows;

namespace VisualBrushSample {
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        private void Button1_Click(object sender, RoutedEventArgs e) {
            MediaElement1.Source = new System.Uri("C:\\projects\\k-on_oped.mp4");
            MediaElement1.Play();
        }

        private void Button2_Click(object sender, RoutedEventArgs e) {
            if (MediaElement1.Source == null) return;
            MediaElement1.Pause();
        }

        private void Button3_Click(object sender, RoutedEventArgs e) {
            if (MediaElement1.Source == null) return;
            MediaElement1.Stop();
            MediaElement1.Source = null;
        }
    }
}

実行するとこうなります。


VisualBrush

お次は VisualBrush の例を見ていきます。Rectangle を一つ用意して、MediaElement の描画内容を転送して描画します。
Rectangle を VisualBrush で塗りつぶすわけですが、Visual="{Binding ElementName=MediaElement1}" という具合に、バインディング対象となるコントロールに MediaElement を指定します。

MainWindow.xaml

<Window x:Class="VisualBrushSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="MainWindow" Height="380" Width="700">
    <Grid>
        <MediaElement Height="160" Width="280" HorizontalAlignment="Left" Name="MediaElement1" 
           VerticalAlignment="Top" LoadedBehavior="Manual" UnloadedBehavior="Manual" Stretch="Fill" />
        <Button Content="Play" Height="30" 
                HorizontalAlignment="Left" Margin="580,11,0,0" 
                Name="Button1" VerticalAlignment="Top" Width="90" Click="Button1_Click" />
        <Button Content="Pause" Height="30" 
                HorizontalAlignment="Left" Margin="580,55,0,0" 
                Name="Button2" VerticalAlignment="Top" Width="90" Click="Button2_Click" />
        <Button Content="Stop" Height="30" 
                HorizontalAlignment="Left" Margin="580,103,0,0" 
                Name="Button3" VerticalAlignment="Top" Width="90" Click="Button3_Click" />
        <Rectangle  HorizontalAlignment="Left" Margin="0,172,0,0" 
          Name="Rectangle1" Stroke="Black" VerticalAlignment="Top" Height="160" Width="280" Stretch="Fill">
            <Rectangle.Fill>
                <VisualBrush TileMode="None" Visual="{Binding ElementName=MediaElement1}" />
            </Rectangle.Fill>
        </Rectangle>
    </Grid>
</Window>


実行すると、こんな感じになります。上が MediaElement、下が Rectangle で VisualBrush を使って MediaElement の表示内容を描画しています。


Rectangle を三つにするとこうなる・・・

MainWindow.xaml

<Window x:Class="VisualBrushSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="MainWindow" Height="380" Width="700">
    <Grid>
        <MediaElement Height="160" Width="280" HorizontalAlignment="Left" Name="MediaElement1" 
           VerticalAlignment="Top" LoadedBehavior="Manual" UnloadedBehavior="Manual" Stretch="Fill" />
        <Button Content="Play" Height="30" 
                HorizontalAlignment="Left" Margin="580,11,0,0" 
                Name="Button1" VerticalAlignment="Top" Width="90" Click="Button1_Click" />
        <Button Content="Pause" Height="30" 
                HorizontalAlignment="Left" Margin="580,55,0,0" 
                Name="Button2" VerticalAlignment="Top" Width="90" Click="Button2_Click" />
        <Button Content="Stop" Height="30" 
                HorizontalAlignment="Left" Margin="580,103,0,0" 
                Name="Button3" VerticalAlignment="Top" Width="90" Click="Button3_Click" />
        <Rectangle  HorizontalAlignment="Left" Margin="0,172,0,0" 
          Name="Rectangle1" Stroke="Black" VerticalAlignment="Top" Height="160" Width="280" Stretch="Fill">
            <Rectangle.Fill>
                <VisualBrush TileMode="None" Visual="{Binding ElementName=MediaElement1}" />
            </Rectangle.Fill>
        </Rectangle>
        <Rectangle HorizontalAlignment="Left" Margin="290,0,0,0" 
          Name="Rectangle2" Stroke="Black" VerticalAlignment="Top" Height="160" Width="280" Stretch="Fill">
            <Rectangle.Fill>
                <VisualBrush TileMode="None" Visual="{Binding ElementName=MediaElement1}" />
            </Rectangle.Fill>
        </Rectangle>
        <Rectangle HorizontalAlignment="Left" Margin="290,172,0,0" 
          Name="Rectangle3" Stroke="Black" VerticalAlignment="Top" Height="160" Width="280" Stretch="Fill">
            <Rectangle.Fill>
                <VisualBrush TileMode="None" Visual="{Binding ElementName=MediaElement1}" />
            </Rectangle.Fill>
        </Rectangle>
    </Grid>
</Window>


F5 キーで実行するとこうなりました。PAUSE ボタンで一時停止してみましたが、同期してるのが判ると思います。



次のネタは 「WindowsForms 開発者のための WPF 超早わかりQ&A」 です。