- podder.zip
- Podder
- Podder.exe
- Podder.exe.config
- PodderLib.dll
- Skins
- GrantHinkson
- Infragistics.ToyBox.dll
- Infragistics3.WPF.DataPresenter.v7.2.dll
- Infragistics3.WPF.Editors.v7.2.dll
- Infragistics3.WPF.v7.2.dll
- Podder.GrantHinkson.dll
- podder_src.zip
- Podder_src
|
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Threading;
using Podder.Model;
namespace Podder
{
/// <summary>
/// A MediaPlayer subclass specialized to play podcast episodes.
/// This class is chock full of workarounds for MediaPlayer bugs and shortcomings.
/// </summary>
internal class EpisodePlayer : MediaPlayer
{
#region Data
// This is part of a workaround for a MediaPlayer quirk.
const int ATTEMPTS_BEFORE_RESTART = 5;
bool _downloadHasBegun;
readonly DispatcherTimer _downloadTimer;
int _numberOfCompletedDownloadProgressValues;
readonly DispatcherTimer _positionTimer;
readonly EpisodePlayerSettings _settings;
bool _wasPlayingBeforeLostNetworkConnection;
#endregion // Data
#region Constructor
public EpisodePlayer()
{
_downloadTimer = new DispatcherTimer(DispatcherPriority.Normal);
_downloadTimer.Interval = TimeSpan.FromSeconds(1);
_downloadTimer.Tick += this.OnEpisodeDownloadTimerTick;
_positionTimer = new DispatcherTimer(DispatcherPriority.Send);
_positionTimer.Interval = TimeSpan.FromSeconds(1);
_positionTimer.Tick += this.OnPositionTimerTick;
_settings = PodderDataSource.Instance.EpisodePlayerSettings;
_settings.PropertyChanged += this.OnEpisodePlayerSettingsPropertyChanged;
NetworkConnection networkConn = PodderDataSource.Instance.NetworkConnection;
networkConn.PropertyChanged += delegate
{
if (!networkConn.IsAvailable && _settings.IsPlaying)
{
this.PauseEpisode();
_wasPlayingBeforeLostNetworkConnection = true;
}
else if (networkConn.IsAvailable && _wasPlayingBeforeLostNetworkConnection)
{
this.PlayEpisode();
_wasPlayingBeforeLostNetworkConnection = false;
}
};
}
#endregion // Constructor
#region Public Interface
/// <summary>
/// Returns true if the specified position can be seeked to.
/// </summary>
public bool CanSeekTo(TimeSpan position)
{
// Don't let the user seek to the end, because then the episode
// immediately ends, which can be confusing. Also sometimes the
// MediaPlayer does not raise the MediaEnded event if you seek
// too close to the end of the episode, so we disallow that.
if (base.NaturalDuration.HasTimeSpan)
{
TimeSpan duration = base.NaturalDuration.TimeSpan;
duration = duration.Subtract(TimeSpan.FromSeconds(1));
if (position < duration)
return true;
}
return false;
}
/// <summary>
/// Raised when EpisodePlayer forces the active episode to stop playing (this is a workaround).
/// </summary>
public event EventHandler MediaForcedToEnd;
public void PauseEpisode()
{
base.Pause();
_settings.SetIsPaused(true);
}
public void PlayEpisode()
{
base.Play();
_settings.SetIsPaused(false);
this.AdjustVolume();
}
#endregion // Public Interface
#region Handle EpisodePlayerSettings Property Changed
void OnEpisodePlayerSettingsPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "ActiveEpisode")
{
this.OnActiveEpisodeChanged();
}
else if (e.PropertyName == "Volume" || e.PropertyName == "IsMuted")
{
this.AdjustVolume();
}
}
#endregion // Handle EpisodePlayerSettings Property Changed
#region Process ActiveEpisode Changed
void OnActiveEpisodeChanged()
{
Episode activeEpisode = _settings.ActiveEpisode;
if (activeEpisode != null)
{
base.Open(new Uri(activeEpisode.Url));
this.PlayEpisode();
bool isLocalFile = base.Source.IsLoopback;
if (!isLocalFile)
_downloadTimer.Start();
_positionTimer.Start();
}
else
{
if (_downloadTimer.IsEnabled)
_downloadTimer.Stop();
if (_positionTimer.IsEnabled)
_positionTimer.Stop();
base.Stop();
base.Close();
_settings.PropertyChanged -= this.OnEpisodePlayerSettingsPropertyChanged;
}
}
#endregion // Process ActiveEpisode Changed
#region MP3 Downloading Workaround
/// <summary>
/// Invoked periodically while the active episode MP3 file is being downloaded.
/// </summary>
void OnEpisodeDownloadTimerTick(object sender, EventArgs e)
{
Debug.WriteLine("Download Progress: " + base.DownloadProgress);
bool haveNaturalDuration = !_settings.HasActiveEpisodeLength && base.NaturalDuration.HasTimeSpan;
if (haveNaturalDuration)
this.SetActiveEpisodeLength();
// This is a workaround for strange MediaPlayer behavior.
if (this.ShouldIgnoreCurrentDownloadProgress)
return;
// If we made it to five tries and the DownloadProgress is still 1.0, then restarting
// the MediaPlayer is the only way to force it to start cooperating.
bool needToRestart =
base.DownloadProgress == 1.0 &&
_numberOfCompletedDownloadProgressValues == ATTEMPTS_BEFORE_RESTART;
if (needToRestart)
{
this.Restart();
}
else
{
// If we reach this point, the download has started and we are good to go.
_downloadHasBegun = true;
if (!_settings.IsActiveEpisodeDownloaded)
this.UpdateActiveEpisodeDownloadPercent();
// If we have everything we need, stop the download timer.
if (_settings.IsActiveEpisodeDownloaded && _settings.HasActiveEpisodeLength)
_downloadTimer.Stop();
}
}
/// <summary>
/// Tries to kickstart the MediaPlayer so that it will correctly download the MP3 file.
/// </summary>
void Restart()
{
Debug.WriteLine("Restarting the EpisodePlayer...");
_downloadHasBegun = false;
_numberOfCompletedDownloadProgressValues = 0;
Uri src = this.Source;
base.Close();
base.Open(src);
this.PlayEpisode();
}
/// <summary>
/// For some strange reason the MediaPlayer's DownloadProgress sometimes returns 1.0 for a little
/// while after you first assign it a new episode to play. To workaround this behavior
/// I ignore any value of 1.0 until it has provided a smaller value first. However, sometimes
/// an episode always reports DownloadProgress as 1.0. In that situation we need to
/// restart the MediaPlayer, which usually fixes the problem.
/// </summary>
bool ShouldIgnoreCurrentDownloadProgress
{
get
{
bool mediaPlayerIsBeingStupid =
!_downloadHasBegun &&
base.DownloadProgress == 1.0 &&
++_numberOfCompletedDownloadProgressValues < ATTEMPTS_BEFORE_RESTART;
return mediaPlayerIsBeingStupid;
}
}
void SetActiveEpisodeLength()
{
// Let the UI know the episodes total length.
_settings.ActiveEpisodeLength = (int)base.NaturalDuration.TimeSpan.TotalMilliseconds + 1;
Debug.WriteLine("Found natural duration of active episode: " + _settings.ActiveEpisode.Name);
}
void UpdateActiveEpisodeDownloadPercent()
{
// Let the UI know how much of the episode we have downloaded so far.
if (base.DownloadProgress == 1.0)
{
_settings.ActiveEpisodeDownloadPercent = 100;
Debug.WriteLine("Finished download of active episode: " + _settings.ActiveEpisode.Name);
}
else if (0.0 < base.DownloadProgress)
{
_settings.ActiveEpisodeDownloadPercent = (int)(base.DownloadProgress * 100.0);
}
}
#endregion // MP3 Downloading Workaround
#region Update Volume
void AdjustVolume()
{
int rawVolume = _settings.Volume;
double volume = (double)rawVolume / 100.0;
base.Volume = volume;
base.IsMuted = _settings.IsMuted;
}
#endregion // Update Volume
#region OnPositionTimerTick
void OnPositionTimerTick(object sender, EventArgs e)
{
if (_settings.IsPaused)
return;
// MediaPlayer will sometimes continue to play past the end of the episode.
// This code prevents it from doing so, by killing the play session.
if (this.IsPlayingPastEndOfEpisode)
{
if (_positionTimer.IsEnabled)
_positionTimer.Stop();
if (_downloadTimer.IsEnabled)
_downloadTimer.Stop();
base.Stop();
if (this.MediaForcedToEnd != null)
this.MediaForcedToEnd(this, EventArgs.Empty);
}
// Setting this property allows the UI to know that the playback position changed.
_settings.ActiveEpisodePosition = (int)base.Position.TotalMilliseconds;
bool haveNaturalDuration = !_settings.HasActiveEpisodeLength && base.NaturalDuration.HasTimeSpan;
if (haveNaturalDuration)
{
this.SetActiveEpisodeLength();
this.UpdateActiveEpisodeDownloadPercent();
}
}
bool IsPlayingPastEndOfEpisode
{
get
{
return
_settings.IsPlaying &&
base.NaturalDuration.HasTimeSpan &&
base.NaturalDuration.TimeSpan < base.Position;
}
}
#endregion // OnPositionTimerTick
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.