none
UWPアプリ (MediaElementで埋め込み音声ファイル使用) の timelineSliderで進行を表示する方法 RRS feed

  • 質問

  • 音声の「スタート・一時停止・一時停止時点からの再開・Stop」、音量調節はできますが、timelineSliderに進行表示をすることができず、0の位置のままです。 (timelineSliderの表示を手で変更して、いろいろな時点から音声実行、一時停止、その時点からの再開はできます。) 

    MSDN ライブラリ 「方法 : MediaElement (再生、一時停止、停止、ボリューム、および速度) を制御する」を参考にしています。(「再生速度を調整」は行っていません。)  https://msdn.microsoft.com/ja-jp/library/ms748248(v=vs.110).aspx   宜しくお願いします。

    2018年1月20日 14:57

回答

  • 1秒間隔で繰り返してしまうのは、タイマーでスライダーの現在値を変更するとスライダーの現在値が変更されたときのValueChangedイベントが発生しますが、イベントが呼び出されるときにはすでに再生中の現在位置は先に進んでいるので、巻き戻し状態になってしまっているのでしょう。
    1ミリ秒間隔は巻き戻しが頻繁に行われてしまうために値がほとんど変化していない状態に見えるのでしょう。

    #追記2018/01/21 1845
    元のサンプルページのコードだとSeekToMediaPositionでスライダーの値をint型にキャストしていて切り捨てが発生しているから、再生位置の秒数が整数になってしまい、巻き戻しが発生している可能性のが高いです。
    #追記ここまで

    ユーザーがスライダーを操作したときだけ、スライダーの値で再生位置(Position)を変更すればよいかと。

    <Page
        x:Class="App1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <StackPanel Background="Black">
            <MediaElement Source="Assets\a.mp4" Name="myMediaElement" 
                          Width="450" Height="250"  Stretch="Fill" 
                          MediaOpened="Element_MediaOpened" 
                          MediaEnded="Element_MediaEnded"/>
    
            <StackPanel  HorizontalAlignment="Center" Width="450" Orientation="Horizontal">
                <Button Margin="5" Tapped="Play_Tapped" >
                    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE768;" Foreground="White" FontSize="30"/>
                </Button>
    
                <Button Margin="5" Tapped="Pause_Tapped">
                    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE769;" Foreground="White" FontSize="30"/>
                </Button>
    
                <Button Margin="5" Tapped="Stop_Tapped">
                    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE71A;" Foreground="White" FontSize="30"/>
                </Button>
            </StackPanel>
    
            <TextBlock Foreground="White" VerticalAlignment="Center" Margin="5" > <Run xml:space="preserve">Volume : </Run><Run Text="{Binding ElementName=volumeSlider,Path=Value}"/></TextBlock>
            <Slider Name="volumeSlider" VerticalAlignment="Center" HorizontalAlignment="Stretch"
                    ValueChanged="ChangeMediaVolume" Minimum="0" Maximum="1" Value="0.5" StepFrequency="0.01" />
    
            <TextBlock Foreground="White" Margin="5"  VerticalAlignment="Center" > <Run xml:space="preserve">Speed : </Run><Run Text="{Binding ElementName=speedRatioSlider,Path=Value}"/></TextBlock>
            <Slider Name="speedRatioSlider" VerticalAlignment="Center" HorizontalAlignment="Stretch"
                    ValueChanged="ChangeMediaSpeedRatio" Value="1" Minimum="0" Maximum="10" StepFrequency="0.1" MinWidth="70" />
    
            <TextBlock Foreground="White" Margin="5"  VerticalAlignment="Center">
                <Run Text="Seek To : "/>
                <Run  >
                    <Run.Text>
                        <Binding ElementName="timelineSlider" Path="Value">
                            <Binding.Converter>
                                <local:TimeLineConverter />
                            </Binding.Converter>
                        </Binding>
                    </Run.Text>
                </Run>
            </TextBlock>
            <Slider Name="timelineSlider" VerticalAlignment="Center" HorizontalAlignment="Stretch"
                    ValueChanged="SeekToMediaPosition" 
                    IsThumbToolTipEnabled="False">
            </Slider>
        </StackPanel>
    </Page>



    using System;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Input;
    
    namespace App1
    {
        public sealed partial class MainPage : Page
        {
            DispatcherTimer timer = new DispatcherTimer();
    
            public MainPage()
            {
                this.InitializeComponent();
    
                this.Loaded += MainPage_Loaded;
            }
    
            private void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                // スライダーのPointerPressedとPointerReleasedのイベントを補足できるようにする
                this.timelineSlider.AddHandler(PointerPressedEvent, new PointerEventHandler(timelineSlider_PointerPressed), true);
                this.timelineSlider.AddHandler(PointerReleasedEvent, new PointerEventHandler(timelineSlider_PointerReleased), true);
    
                timer.Interval = TimeSpan.FromSeconds(0.1);
                timer.Tick += Timer_Tick;
                timer.Start();
            }
    
            private void Play_Tapped(object sender, TappedRoutedEventArgs e)
            {
                myMediaElement.Play();
            }
    
            private void Pause_Tapped(object sender, TappedRoutedEventArgs e)
            {
                myMediaElement.Pause();
            }
    
            private void Stop_Tapped(object sender, TappedRoutedEventArgs e)
            {
                myMediaElement.Stop();
            }
    
            private void Element_MediaOpened(object sender, RoutedEventArgs e)
            {
                //スライダーの最大値をメディアの再生秒数になるように
                this.timelineSlider.Minimum = 0;
                this.timelineSlider.Maximum = myMediaElement.NaturalDuration.TimeSpan.TotalSeconds;
                this.timelineSlider.Value = 0;
    
                timer.Start();
            }
    
            private void Element_MediaEnded(object sender, RoutedEventArgs e)
            {
                timer.Stop();
            }
    
            private void ChangeMediaVolume(object sender, RangeBaseValueChangedEventArgs e)
            {
                myMediaElement.Volume = (double)volumeSlider.Value;
            }
    
            private void ChangeMediaSpeedRatio(object sender, RangeBaseValueChangedEventArgs e)
            {
                myMediaElement.PlaybackRate = (double)speedRatioSlider.Value;
            }
    
    
            bool isSliderUserChanging = false;//ユーザー操作中
    
            private void Timer_Tick(object sender, object e)
            {
                //タイマーで現在の再生位置を更新する
                this.timelineSlider.Value = myMediaElement.Position.TotalSeconds;
            }
    
            private void timelineSlider_PointerPressed(object sender, PointerRoutedEventArgs e)
            {
                //ユーザーがスライダー操作を開始したらタイマーでの更新を止める
                timer.Stop();
                isSliderUserChanging = true;
            }
    
            private void timelineSlider_PointerReleased(object sender, PointerRoutedEventArgs e)
            {
                //ユーザーがスライダー操作を開始したらタイマーでの更新を再開する
                timer.Start();
                isSliderUserChanging = false;
    
                //Thumb以外の部分をクリックして値が変更されている場合に更新されるように
                TimeSpan ts = TimeSpan.FromSeconds(timelineSlider.Value);
                myMediaElement.Position = ts;
            }
    
            private void SeekToMediaPosition(object sender, RangeBaseValueChangedEventArgs e)
            {
                if (isSliderUserChanging)
                {
                    //ユーザー操作でない場合はスライダーの位置を更新する
                    TimeSpan ts = TimeSpan.FromSeconds(timelineSlider.Value);
                    myMediaElement.Position = ts;
                }
            }
        }
    
        class TimeLineConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, string language)
            {
                if (value is IConvertible)
                {
                    double d = ((IConvertible)value).ToDouble(null);
                    d = Math.Floor(d);
                    value = TimeSpan.FromSeconds(d);
                }
                return value;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, string language)
            {
                throw new NotImplementedException();
            }
        }
    }

    timeSliderのMaximumプロパティを再生するメディアの再生秒数(NaturalDuration)に設定すると楽です。



    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)


    • 編集済み gekkaMVP 2018年1月21日 9:47 スライダーの値の切り捨てに関しての記述を追記
    • 回答としてマーク nysky 2018年1月21日 12:45
    2018年1月21日 8:35

すべての返信

  • 一応あるBlogを参考に DispatcherTimerを試してみました。 

    ①単位:1ミリ秒 使用(_timerInterval = 1000に設定)の時は、timelineSliderの表示に変化はなく0のままですが、一時停止・その時点からの再開などOKでした。  

    ②単位:1秒 使用(_timerInterval = 1に設定)の時は、timelineSliderの表示は進行していくのですが、1秒ごと位に1秒位の短い同じ音声をずっと繰り返します。 

    Timerの使用時に、単位はどのように設定したらいいでしょうか? また、timelineSliderのプロパティの細かい設定方法がわかりません。何か設定すべきプロパティがあればお願いします。 

     ② public sealed partial class MainPage : Page
             {               
                private DispatcherTimer _timer = new DispatcherTimer();
                private const int _timerInterval = 1;
                private int _elapsedSeconds = 0;
                private bool _sliderValueChangedByProgram = false;

                public MainPage()
                {
                    this.InitializeComponent();
                    _timer.Interval = new TimeSpan(0, 0, _timerInterval);
                    _timer.Tick += dispatcherTimer_Tick;
                }
                private void dispatcherTimer_Tick(object sender, object e)
                {
                    _elapsedSeconds += _timerInterval;
                    double totalSeconds = mediaElement1.NaturalDuration.TimeSpan.TotalSeconds;
                    timelineSlider.Value = _elapsedSeconds / totalSeconds * timelineSlider.Maximum;
                    _sliderValueChangedByProgram = true;    
                }   

    2018年1月21日 5:08
  • 1秒間隔で繰り返してしまうのは、タイマーでスライダーの現在値を変更するとスライダーの現在値が変更されたときのValueChangedイベントが発生しますが、イベントが呼び出されるときにはすでに再生中の現在位置は先に進んでいるので、巻き戻し状態になってしまっているのでしょう。
    1ミリ秒間隔は巻き戻しが頻繁に行われてしまうために値がほとんど変化していない状態に見えるのでしょう。

    #追記2018/01/21 1845
    元のサンプルページのコードだとSeekToMediaPositionでスライダーの値をint型にキャストしていて切り捨てが発生しているから、再生位置の秒数が整数になってしまい、巻き戻しが発生している可能性のが高いです。
    #追記ここまで

    ユーザーがスライダーを操作したときだけ、スライダーの値で再生位置(Position)を変更すればよいかと。

    <Page
        x:Class="App1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <StackPanel Background="Black">
            <MediaElement Source="Assets\a.mp4" Name="myMediaElement" 
                          Width="450" Height="250"  Stretch="Fill" 
                          MediaOpened="Element_MediaOpened" 
                          MediaEnded="Element_MediaEnded"/>
    
            <StackPanel  HorizontalAlignment="Center" Width="450" Orientation="Horizontal">
                <Button Margin="5" Tapped="Play_Tapped" >
                    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE768;" Foreground="White" FontSize="30"/>
                </Button>
    
                <Button Margin="5" Tapped="Pause_Tapped">
                    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE769;" Foreground="White" FontSize="30"/>
                </Button>
    
                <Button Margin="5" Tapped="Stop_Tapped">
                    <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE71A;" Foreground="White" FontSize="30"/>
                </Button>
            </StackPanel>
    
            <TextBlock Foreground="White" VerticalAlignment="Center" Margin="5" > <Run xml:space="preserve">Volume : </Run><Run Text="{Binding ElementName=volumeSlider,Path=Value}"/></TextBlock>
            <Slider Name="volumeSlider" VerticalAlignment="Center" HorizontalAlignment="Stretch"
                    ValueChanged="ChangeMediaVolume" Minimum="0" Maximum="1" Value="0.5" StepFrequency="0.01" />
    
            <TextBlock Foreground="White" Margin="5"  VerticalAlignment="Center" > <Run xml:space="preserve">Speed : </Run><Run Text="{Binding ElementName=speedRatioSlider,Path=Value}"/></TextBlock>
            <Slider Name="speedRatioSlider" VerticalAlignment="Center" HorizontalAlignment="Stretch"
                    ValueChanged="ChangeMediaSpeedRatio" Value="1" Minimum="0" Maximum="10" StepFrequency="0.1" MinWidth="70" />
    
            <TextBlock Foreground="White" Margin="5"  VerticalAlignment="Center">
                <Run Text="Seek To : "/>
                <Run  >
                    <Run.Text>
                        <Binding ElementName="timelineSlider" Path="Value">
                            <Binding.Converter>
                                <local:TimeLineConverter />
                            </Binding.Converter>
                        </Binding>
                    </Run.Text>
                </Run>
            </TextBlock>
            <Slider Name="timelineSlider" VerticalAlignment="Center" HorizontalAlignment="Stretch"
                    ValueChanged="SeekToMediaPosition" 
                    IsThumbToolTipEnabled="False">
            </Slider>
        </StackPanel>
    </Page>



    using System;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Input;
    
    namespace App1
    {
        public sealed partial class MainPage : Page
        {
            DispatcherTimer timer = new DispatcherTimer();
    
            public MainPage()
            {
                this.InitializeComponent();
    
                this.Loaded += MainPage_Loaded;
            }
    
            private void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                // スライダーのPointerPressedとPointerReleasedのイベントを補足できるようにする
                this.timelineSlider.AddHandler(PointerPressedEvent, new PointerEventHandler(timelineSlider_PointerPressed), true);
                this.timelineSlider.AddHandler(PointerReleasedEvent, new PointerEventHandler(timelineSlider_PointerReleased), true);
    
                timer.Interval = TimeSpan.FromSeconds(0.1);
                timer.Tick += Timer_Tick;
                timer.Start();
            }
    
            private void Play_Tapped(object sender, TappedRoutedEventArgs e)
            {
                myMediaElement.Play();
            }
    
            private void Pause_Tapped(object sender, TappedRoutedEventArgs e)
            {
                myMediaElement.Pause();
            }
    
            private void Stop_Tapped(object sender, TappedRoutedEventArgs e)
            {
                myMediaElement.Stop();
            }
    
            private void Element_MediaOpened(object sender, RoutedEventArgs e)
            {
                //スライダーの最大値をメディアの再生秒数になるように
                this.timelineSlider.Minimum = 0;
                this.timelineSlider.Maximum = myMediaElement.NaturalDuration.TimeSpan.TotalSeconds;
                this.timelineSlider.Value = 0;
    
                timer.Start();
            }
    
            private void Element_MediaEnded(object sender, RoutedEventArgs e)
            {
                timer.Stop();
            }
    
            private void ChangeMediaVolume(object sender, RangeBaseValueChangedEventArgs e)
            {
                myMediaElement.Volume = (double)volumeSlider.Value;
            }
    
            private void ChangeMediaSpeedRatio(object sender, RangeBaseValueChangedEventArgs e)
            {
                myMediaElement.PlaybackRate = (double)speedRatioSlider.Value;
            }
    
    
            bool isSliderUserChanging = false;//ユーザー操作中
    
            private void Timer_Tick(object sender, object e)
            {
                //タイマーで現在の再生位置を更新する
                this.timelineSlider.Value = myMediaElement.Position.TotalSeconds;
            }
    
            private void timelineSlider_PointerPressed(object sender, PointerRoutedEventArgs e)
            {
                //ユーザーがスライダー操作を開始したらタイマーでの更新を止める
                timer.Stop();
                isSliderUserChanging = true;
            }
    
            private void timelineSlider_PointerReleased(object sender, PointerRoutedEventArgs e)
            {
                //ユーザーがスライダー操作を開始したらタイマーでの更新を再開する
                timer.Start();
                isSliderUserChanging = false;
    
                //Thumb以外の部分をクリックして値が変更されている場合に更新されるように
                TimeSpan ts = TimeSpan.FromSeconds(timelineSlider.Value);
                myMediaElement.Position = ts;
            }
    
            private void SeekToMediaPosition(object sender, RangeBaseValueChangedEventArgs e)
            {
                if (isSliderUserChanging)
                {
                    //ユーザー操作でない場合はスライダーの位置を更新する
                    TimeSpan ts = TimeSpan.FromSeconds(timelineSlider.Value);
                    myMediaElement.Position = ts;
                }
            }
        }
    
        class TimeLineConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, string language)
            {
                if (value is IConvertible)
                {
                    double d = ((IConvertible)value).ToDouble(null);
                    d = Math.Floor(d);
                    value = TimeSpan.FromSeconds(d);
                }
                return value;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, string language)
            {
                throw new NotImplementedException();
            }
        }
    }

    timeSliderのMaximumプロパティを再生するメディアの再生秒数(NaturalDuration)に設定すると楽です。



    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)


    • 編集済み gekkaMVP 2018年1月21日 9:47 スライダーの値の切り捨てに関しての記述を追記
    • 回答としてマーク nysky 2018年1月21日 12:45
    2018年1月21日 8:35
  • 詳細に返信いただき有難うございます。 timelineSliderに進行表示をすることができるようになりました!  手動でポインターを移動させて一時停止、再開してもOKです。有難うございます。  

    一つお願いがあります。Playボタンを押してから音声をStartさせたいのですが‐‐‐。少し試してみたのですがうまくいきません。(急ぎませんので今週中にでも返信頂ければ大変助かります。)

     public sealed partial class MainPage : Page
        {
            DispatcherTimer timer = new DispatcherTimer();
            public MainPage()
            {
                this.InitializeComponent();
                //this.Loaded += MainPage_Loaded;
               
                // スライダーのPointerPressedとPointerReleasedのイベントを補足できるようにする
                this.timelineSlider.AddHandler(PointerPressedEvent, new PointerEventHandler(timelineSlider_PointerPressed), true);
                this.timelineSlider.AddHandler(PointerReleasedEvent, new PointerEventHandler(timelineSlider_PointerReleased), true);
                timer.Interval = TimeSpan.FromSeconds(0.1);
                timer.Tick += Timer_Tick;
            }
            private void Play_Tapped(object sender, TappedRoutedEventArgs e)
            {            
                    timer.Start();
                    myMediaElement.Play();
            }

    2018年1月21日 12:45
  • MediaElementのAutoPlayプロパティをXAMLなどでfalseに設定しておくとPlay()を行うまでは再生されません。

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2018年1月21日 13:31
    2018年1月21日 13:30
  • そうでした! 最初から作り直していてすっかり忘れていました。ありがとうございます。
    2018年1月21日 13:42