Creating AudioPlayerPageViewModel

Our AudioPlayerPageViewModel must include our ISoundHandler interface. We are going to be controlling the audio from this view-model, so our buttons can initiate the required events on the sound handler. Let's begin by making a new file inside the ViewModels folder called AudioPlayerPageViewModel.cs, and implementing the private properties to begin with:

public class AudioPlayerPageViewModel : MvxViewModel 
    { 
        #region Private Properties 
 
        private readonly ISoundHandler _soundHandler; 
 
        private string _title = "Audio Player"; 
 
        private string _descriptionMessage = "Moby - The Only Thing"; 
 
        private MvxCommand _playPauseCommand; 
 
        private MvxCommand _forwardCommand; 
 
        private MvxCommand _rewindCommand; 
 
        private float _audioPosition; 
 
        private double _currentTime; 
 
        private double _endTime; 
 
        private bool _updating; 
 
        #endregion 

Then we must add the public properties.

Note

We are only going to show two of the public properties as examples, as the code is repetitive.

public MvxCommand PlayPauseCommand 
        { 
            get 
            { 
                return _playPauseCommand; 
            } 
 
            set 
            { 
                if (!value.Equals(_playPauseCommand)) 
                { 
                    _playPauseCommand = value; 
                    RaisePropertyChanged (() => PlayPauseCommand); 
                } 
            } 
        } 
 
        public MvxCommand RewindCommand 
        { 
            get 
            { 
                return _rewindCommand; 
            } 
 
            set 
            { 
                if (!value.Equals(_rewindCommand)) 
                { 
                    _rewindCommand = value; 
                    RaisePropertyChanged(() => RewindCommand); 
                } 
            } 
        } 

We also need to add these two public variables, which are going to take both the CurrentTime and EndTime double values and create a formatted string from a TimeSpan value.

Notice how we are also calling RaisePropertyChanged on the string inside the double setter? Every time we get a new current time value, the formatted string needs to update as well:

public string CurrentTimeStr 
        { 
            get 
            { 
                return TimeSpan.FromSeconds(CurrentTime).ToString("mm\:ss"); 
            } 
        } 
 
        public double CurrentTime 
        { 
            get 
            { 
                return _currentTime; 
            } 
 
            set 
            { 
                if (!value.Equals(_currentTime)) 
                { 
                    _currentTime = value; 
                    RaisePropertyChanged(() => CurrentTime); 
                    // everytime we change the current time, the time span values must also update 
                    RaisePropertyChanged(() => CurrentTimeStr); 
                } 
            } 
        } 
 
        public string EndTimeStr 
        { 
            get 
            { 
                return TimeSpan.FromSeconds(EndTime).ToString("mm\:ss"); 
            } 
        } 
 
 
        public double EndTime 
        { 
            get 
            { 
                return _endTime; 
            } 
 
            set 
            { 
                if (!value.Equals(_endTime)) 
                { 
                    _endTime = value; 
                    RaisePropertyChanged(() => EndTime); 
                    RaisePropertyChanged(() => EndTimeStr); 
                } 
            } 
        } 

Now for our constructor function:

#region Constructors 
 
        public AudioPlayerPageViewModel (ISoundHandler soundHandler) 
        { 
            _soundHandler = soundHandler; 
 
            // load sound file 
            _soundHandler.Load(); 
 
            EndTime = _soundHandler.Duration(); 
        } 
 
        #endregion 

Here we are pulling out the ISoundHandler implementation from the IoC container, as we will be registering this view-model inside the IoC container.

Our next step is to add two new functions to the view-model, Load and Dispose. These two functions will be called when the AudioPlayerPage is shown, and when it disappears. They will also be used when the audio stream is started and stopped.

Let's first add the Load function:

public void Load() 
        { 
            // make sure we only start the loop once 
            if (!_updating) 
            { 
                _updating = true; 
 
                // we are going to post a regular update to the UI with the current time 
                var context = SynchronizationContext.Current; 
 
                Task.Run(async () => 
                { 
                    while (_updating) 
                    { 
                        await Task.Delay(1000); 
 
                        context.Post(unused => 
                        { 
                            var current = _soundHandler.CurrentPosition(); ; 
 
                            if (current > 0) 
                            { 
                               CurrentTime = current; 
                            } 
 
                        }, null); 
                    } 
                }); 
            } 
        } 

The Load function will be called when the page is shown, and when the audio stream starts. The function uses the Task framework to run a repeating loop in the background, so every second we will retrieve the current time of the audio stream from the ISoundHandler interface. We propagate the updates to the current time label on the AudioPlayerPage interface.

Notice how we are using the SynchronisationContext.Current variable?

This is used for threading purposes so we make sure that we set our CurrentTime variable on the main UI thread. Since this loop is running on a separate thread, if we made changes to this variable on a separate thread, it will break the application because you are trying to make UI changes off the main UI thread.

Now for the Dispose function; this will be called every time the AudioPlayerPage disappears and when the audio stream is stopped (we don't need to make updates to the UI when the audio stream is not playing). This ensures we stop the background loop when the page is not visible:

public void Dispose() 
        { 
            _updating = false; 
            _soundHandler.Stop(); 
        } 

The private variable _updating is used to control the status of whether the background loop is running, so we make sure that only one background loop is running at any one time.

Now let's initiate the audio commands:

_playPauseCommand = new MvxCommand(() => 
            { 
                // start/stop UI updates if the audio is not playing 
                if (soundHandler.IsPlaying) 
                { 
                    Dispose(); 
                } 
                else 
                { 
                    Load(); 
                } 
 
                _soundHandler.PlayPause(); 
            }); 
 
            _rewindCommand = new MvxCommand(() => 
            { 
                // set current time to the beginning 
                CurrentTime = 0; 
                _soundHandler.Rewind(); 
                Dispose(); 
            }); 
 
            _forwardCommand = new MvxCommand(() => 
            { 
                // set current time to the end 
                CurrentTime = _soundHandler.Duration(); 
                _soundHandler.Forward(); 
                Dispose(); 
            }); 

Looking more closely at these commands, using PlayPauseCommand we will call Load or Dispose based on the playing status of the audio stream, and it will also call PlayPause on the ISoundHandler interface, which controls the audio stream. The rewindCommand property will set the current time to 0, set the current time on the audio stream to 0, and stop the background loop. The forwardCommand property will set the current time to the end duration of the audio stream (which it will retrieve from the ISoundHandler interface), set the current time on the audio stream to the end duration, and stop the background loop.

Finally, we have to create a public function to set the current time of the audio stream. This will be used by our progress slider every time the value changes, this function will be called:

        public void UpdateAudioPosition(double value) 
        { 
            _soundHandler.SetPosition(value); 
        } 

Now revert back to the AudioPlayerPage and add the final additions.

Since we declared a local variable before for the view-model that is bound to the view, we want to pull this out of the data context of the UIView:

_model = (AudioPlayerPageViewModel)DataContext; 

Our local variable has the bounded view-model. We need to call some public methods on the view-model from our view. We must add in our event handler for the ValueChanged event on the progress slider. Add the following under the declaration of the progress slider:

progressSlider.ValueChanged += ProgressSliderValueChanged; 

Then create the event handler function:

        private void ProgressSliderValueChanged(object sender, EventArgs e) 
        { 
            _model.UpdateAudioPosition(_progressSlider.Value); 
        } 

And add the calls to the Load function when the page appears:

public override void ViewDidAppear(bool animated) 
        { 
            _model.Load(); 
 
            base.ViewDidAppear(animated); 
        } 

Override ViewDidDisappear to call the Dispose function:

public override void ViewDidDisappear(bool animated) 
        { 
            _model.Dispose(); 
 
            base.ViewDidDisappear(animated); 
        } 

And create the following bindings in the binding set:

set.Bind(this).For("Title").To(vm => vm.Title); 
            set.Bind(descriptionLabel).To(vm => vm.DescriptionMessage); 
            set.Bind(currentLabel).To(vm => vm.CurrentTime); 
            set.Bind(endLabel).To(vm => vm.EndTime); 
            set.Bind(progressSlider).For(v => v.Value).To(vm => vm.CurrentTime);  
            set.Bind(progressSlider).For(v => v.MaxValue).To(vm => vm.EndTime); 
            set.Bind(playButton).To(vm => vm.PlayPauseCommand); 
            set.Bind(rewindButton).To(vm => vm.RewindCommand); 
            set.Bind(fastForwardButton).To(vm => vm.ForwardCommand); 

We have our labels bound to the description, which are hard coded. This is why we must make changes to the CurrentTime variable on the main UI thread, because it affects what is displayed on the currentLabel. We also have our MvxCommand bindings on our audio buttons. Finally, we have our bindings on the Value property of the progress slider to match the CurrentTime variable, and the MaxValue to match the end time of the audio stream, so it matches the percentage playing time of the audio stream.

Excellent! Try running the application and playing around with the play/pause and progress slider functionality.

Let's move on to building the equivalent for the Android version.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.23.123