50
53
namespace Banshee.GStreamerSharp
52
public class PlayerEngine : Banshee.MediaEngine.PlayerEngine
55
public class PlayerEngine : Banshee.MediaEngine.PlayerEngine, IEqualizer, IVisualizationDataSource
57
private class AudioSinkBin : Bin
59
Element hw_audio_sink;
65
GhostPad visible_sink;
67
object pipeline_lock = new object ();
69
public AudioSinkBin (IntPtr o) : base(o)
74
public AudioSinkBin (string elementName) : base(elementName)
76
hw_audio_sink = SelectAudioSink ();
78
first = hw_audio_sink;
80
// Our audio sink is a tee, so plugins can attach their own pipelines
81
audiotee = ElementFactory.Make ("tee", "audiotee") as Tee;
82
if (audiotee == null) {
83
Log.Error ("Can not create audio tee!");
88
volume = FindVolumeProvider (hw_audio_sink);
90
// If the sink provides its own volume property we assume that it will
91
// also save that value across program runs. Pulsesink has this behaviour.
92
VolumeNeedsSaving = false;
94
volume = ElementFactory.Make ("volume", "volume");
95
VolumeNeedsSaving = true;
97
volume.Link (hw_audio_sink);
101
equalizer = ElementFactory.Make ("equalizer-10bands", "equalizer-10bands");
102
if (equalizer != null) {
103
Element eq_audioconvert = ElementFactory.Make ("audioconvert", "audioconvert");
104
Element eq_audioconvert2 = ElementFactory.Make ("audioconvert", "audioconvert2");
105
preamp = ElementFactory.Make ("volume", "preamp");
107
Add (eq_audioconvert, preamp, equalizer, eq_audioconvert2);
108
Element.Link (eq_audioconvert, preamp, equalizer, eq_audioconvert2, first);
110
first = eq_audioconvert;
111
Log.Debug ("Built and linked Equalizer");
114
// Link the first tee pad to the primary audio sink queue
115
Pad sinkpad = first.GetStaticPad ("sink");
116
Pad pad = audiotee.GetRequestPad ("src%d");
117
audiotee.AllocPad = pad;
121
visible_sink = new GhostPad ("sink", first.GetStaticPad ("sink"));
122
AddPad (visible_sink);
125
static Element FindVolumeProvider (Element sink)
127
Element volumeProvider = null;
128
// Sinks which automatically select between a number of possibilities
129
// (such as autoaudiosink and gconfaudiosink) need to be at least in
130
// the Ready state before they'll contain an actual sink.
131
sink.SetState (State.Ready);
133
if (sink.HasProperty ("volume")) {
134
volumeProvider = sink;
135
Log.DebugFormat ("Sink {0} has native volume.", volumeProvider.Name);
137
var sinkBin = sink as Bin;
138
if (sinkBin != null) {
139
foreach (Element e in sinkBin.ElementsRecurse) {
140
if (e.HasProperty ("volume")) {
142
Log.DebugFormat ("Found volume provider {0} in {1}.",
143
volumeProvider.Name, sink.Name);
148
return volumeProvider;
151
static Element SelectAudioSink ()
153
Element audiosink = null;
155
// Default to GConfAudioSink, which should Do The Right Thing.
156
audiosink = ElementFactory.Make ("gconfaudiosink", "audiosink");
157
if (audiosink == null) {
158
// Try DirectSoundSink, which should work on Windows
159
audiosink = ElementFactory.Make ("directsoundsink", "audiosink");
160
if (audiosink != null) {
161
// The unmanaged code sets the volume on the directsoundsink here.
162
// Presumably this fixes a problem, but there's no reference as to what it is.
163
audiosink["volume"] = 1.0;
165
audiosink = ElementFactory.Make ("autoaudiosink", "audiosink");
166
if (audiosink == null) {
167
// As a last-ditch effort try ALSA.
168
audiosink = ElementFactory.Make ("alsasink", "audiosink");
175
public bool ReplayGainEnabled {
176
get { return rgvolume != null; }
178
if (value && rgvolume == null) {
179
visible_sink.SetBlocked (true, InsertReplayGain);
180
Log.Debug ("Enabled ReplayGain volume scaling.");
181
} else if (!value && rgvolume != null) {
182
visible_sink.SetBlocked (false, RemoveReplayGain);
183
Log.Debug ("Disabled ReplayGain volume scaling.");
188
void InsertReplayGain (Pad pad, bool blocked)
190
lock (pipeline_lock) {
191
if (rgvolume == null) {
192
rgvolume = ElementFactory.Make ("rgvolume", "rgvolume");
194
rgvolume.SyncStateWithParent ();
195
visible_sink.SetTarget (rgvolume.GetStaticPad ("sink"));
196
rgvolume.Link (first);
200
visible_sink.SetBlocked (false, (_, __) => { });
203
void RemoveReplayGain (Pad pad, bool blocked)
205
lock (pipeline_lock) {
206
if (rgvolume != null) {
207
first = rgvolume.GetStaticPad ("src").Peer.Parent as Element;
208
rgvolume.Unlink (first);
209
rgvolume.SetState (State.Null);
212
visible_sink.SetTarget (first.GetStaticPad ("sink"));
215
visible_sink.SetBlocked (false, (_, __) => { });
219
public bool VolumeNeedsSaving { get; private set; }
220
public double Volume {
222
return (double)volume["volume"];
225
if (value < 0 || value > 10.0) {
226
throw new ArgumentOutOfRangeException ("value", "Volume must be between 0 and 10.0");
228
Log.DebugFormat ("Setting volume to {0:0.00}", value);
229
volume["volume"] = value;
233
public bool SupportsEqualizer { get {return preamp != null && equalizer != null;} }
235
public double AmplifierLevel {
236
set { preamp ["volume"] = Math.Pow (10.0, value / 20.0); }
239
public int [] BandRange {
244
PropertyInfo pspec = new PropertyInfo();
246
if (equalizer.HasProperty ("band0::gain")) {
247
pspec = equalizer.GetPropertyInfo ("band0::gain");
248
} else if (equalizer.HasProperty ("band0")) {
249
pspec = equalizer.GetPropertyInfo ("band0");
252
if (pspec.Name != null) {
253
min = (int)((double)pspec.Min);
254
max = (int)((double)pspec.Max);
257
return new int [] { min, max };
261
private uint GetNBands ()
263
if (equalizer == null) {
267
return ChildProxyAdapter.GetObject (equalizer).ChildrenCount;
270
public uint [] EqualizerFrequencies {
272
uint count = GetNBands ();
273
uint[] ret = new uint[count];
275
if (equalizer != null) {
276
ChildProxy equalizer_child_proxy = ChildProxyAdapter.GetObject (equalizer);
277
for (uint i = 0; i < count; i++) {
278
Gst.Object band = equalizer_child_proxy.GetChildByIndex (i);
279
ret [i] = (uint)(double)band ["freq"];
287
public void SetEqualizerGain (uint band, double value)
289
if (equalizer != null) {
290
if (band >= GetNBands ()) {
291
throw new ArgumentOutOfRangeException ("band", "Attempt to set out-of-range equalizer band");
293
Gst.Object the_band = ChildProxyAdapter.GetObject (equalizer).GetChildByIndex (band);
294
the_band ["gain"] = value;
298
public Pad RequestTeePad ()
300
return audiotee.GetRequestPad ("src%d");
306
AudioSinkBin audio_sink;
56
307
uint iterate_timeout_id = 0;
308
List<string> missing_details = new List<string> ();
309
ManualResetEvent next_track_set;
310
CddaManager cdda_manager;
311
VideoManager video_manager = null;
312
Visualization visualization;
58
314
public PlayerEngine ()
81
337
Gst.Application.Init ();
82
pipeline = new Pipeline ();
83
338
playbin = new PlayBin2 ();
84
pipeline.Add (playbin);
86
// Remember the volume from last time
87
Volume = (ushort)PlayerEngineService.VolumeSchema.Get ();
340
next_track_set = new ManualResetEvent (false);
342
audio_sink = new AudioSinkBin ("audiobin");
344
playbin["audio-sink"] = audio_sink;
346
if (audio_sink.VolumeNeedsSaving) {
347
// Remember the volume from last time
348
Volume = (ushort)PlayerEngineService.VolumeSchema.Get ();
351
Pad teepad = audio_sink.RequestTeePad ();
352
visualization = new Visualization (audio_sink, teepad);
89
354
playbin.AddNotification ("volume", OnVolumeChanged);
90
pipeline.Bus.AddWatch (OnBusMessage);
355
playbin.Bus.AddWatch (OnBusMessage);
356
playbin.AboutToFinish += OnAboutToFinish;
358
cdda_manager = new CddaManager (playbin);
359
// FIXME: Disable video stuff until GLib# 3 is used instead of the sopy bundled in GStreamerSharp
360
//video_manager = new VideoManager (playbin);
361
//video_manager.PrepareWindow += OnVideoPrepareWindow;
362
//video_manager.Initialize ();
92
364
OnStateChanged (PlayerState.Ready);
367
protected override bool DelayedInitialize {
373
protected override void Initialize ()
376
InstallPreferences ();
377
audio_sink.ReplayGainEnabled = ReplayGainEnabledSchema.Get ();
380
public override void Dispose ()
382
UninstallPreferences ();
386
public event VisualizationDataHandler DataAvailable {
388
visualization.DataAvailable += value;
392
visualization.DataAvailable -= value;
396
public override void VideoExpose (IntPtr window, bool direct)
398
video_manager.WindowExpose (window, direct);
401
public override void VideoWindowRealize (IntPtr window)
403
video_manager.WindowRealize (window);
406
private void OnVideoPrepareWindow ()
408
OnEventChanged (PlayerEvent.PrepareVideoWindow);
411
void OnAboutToFinish (object o, Gst.GLib.SignalArgs args)
413
// This is needed to make Shuffle-by-* work.
414
// Shuffle-by-* uses the LastPlayed field to determine what track in the grouping to play next.
415
// Therefore, we need to update this before requesting the next track.
417
// This will be overridden by IncrementLastPlayed () called by
418
// PlaybackControllerService's EndOfStream handler.
419
CurrentTrack.UpdateLastPlayed ();
421
next_track_set.Reset ();
422
OnEventChanged (PlayerEvent.RequestNextTrack);
424
if (!next_track_set.WaitOne (1000, false)) {
425
Log.Warning ("[Gapless]: Timed out while waiting for next track to be set.");
426
next_track_set.Set ();
430
public override void SetNextTrackUri (SafeUri uri, bool maybeVideo)
432
if (next_track_set.WaitOne (0, false)) {
433
// We've been asked to set the next track, but have taken too
434
// long to get here. Bail for now, and the EoS handling will
435
// pick up the pieces.
438
playbin.Uri = uri.AbsoluteUri;
441
LookupForSubtitle (uri);
444
next_track_set.Set ();
95
447
private bool OnBusMessage (Bus bus, Message msg)
97
449
switch (msg.Type) {
128
482
msg.ParseError (out error_type, out err_msg, out debug);
130
484
HandleError (error_type, err_msg, debug);
487
case MessageType.Element:
488
if (MissingPluginMessage.IsMissingPluginMessage (msg)) {
489
string detail = MissingPluginMessage.GetInstallerDetail (msg);
494
if (missing_details.Contains (detail)) {
495
Log.DebugFormat ("Ignoring missing element details, already prompted ('{0}')", detail);
499
Log.DebugFormat ("Saving missing element details ('{0}')", detail);
500
missing_details.Add (detail);
502
Log.Error ("Missing GStreamer Plugin", MissingPluginMessage.GetDescription (msg), true);
504
InstallPluginsContext install_context = new InstallPluginsContext ();
505
Install.InstallPlugins (missing_details.ToArray (), install_context, OnInstallPluginsReturn);
506
} else if (msg.Src == playbin && msg.Structure.Name == "playbin2-stream-changed") {
507
HandleStreamChanged ();
510
case MessageType.Application:
512
Structure s = msg.Structure;
514
if (String.IsNullOrEmpty (name) && name == "stream-changed") {
515
video_manager.ParseStreamInfo ();
523
private void OnInstallPluginsReturn (InstallPluginsReturn status)
525
Log.InformationFormat ("GStreamer plugin installer returned: {0}", status);
526
if (status == InstallPluginsReturn.Success || status == InstallPluginsReturn.InstallInProgress) {
138
530
private void OnVolumeChanged (object o, Gst.GLib.NotifyArgs args)
140
532
OnEventChanged (PlayerEvent.Volume);
535
private void HandleStreamChanged ()
537
// Set the current track as fully played before signaling EndOfStream.
538
ServiceManager.PlayerEngine.IncrementLastPlayed (1.0);
539
OnEventChanged (PlayerEvent.EndOfStream);
540
OnEventChanged (PlayerEvent.StartOfStream);
143
543
private void HandleError (Enum domain, string error_message, string debug)
262
662
protected override void OpenUri (SafeUri uri, bool maybeVideo)
264
if (pipeline.CurrentState == State.Playing || pipeline.CurrentState == State.Paused) {
265
pipeline.SetState (Gst.State.Ready);
664
if (cdda_manager.HandleURI (playbin, uri.AbsoluteUri)) {
666
} else if (playbin == null) {
667
throw new ApplicationException ("Could not open resource");
670
if (playbin.CurrentState == State.Playing || playbin.CurrentState == State.Paused) {
671
playbin.SetState (Gst.State.Ready);
268
674
playbin.Uri = uri.AbsoluteUri;
676
// Lookup for subtitle files with same name/folder
677
LookupForSubtitle (uri);
681
private void LookupForSubtitle (SafeUri uri)
683
string scheme, filename, subfile;
686
// Always enable rendering of subtitles
688
flags = (int)playbin.Flags;
689
flags |= (1 << 2);//GST_PLAY_FLAG_TEXT
690
playbin.Flags = (ObjectFlags)flags;
692
Log.Debug ("[subtitle]: looking for subtitles for video file");
694
string[] subtitle_extensions = { ".srt", ".sub", ".smi", ".txt", ".mpl", ".dks", ".qtx" };
695
if (scheme == null || scheme == "file") {
699
dot = uri.AbsoluteUri.LastIndexOf ('.');
703
filename = uri.AbsoluteUri.Substring (0, dot);
705
foreach (string extension in subtitle_extensions) {
706
subfile = filename + extension;
707
suburi = new SafeUri (subfile);
708
if (Banshee.IO.File.Exists (suburi)) {
709
Log.DebugFormat ("[subtitle]: Found subtitle file: {0}", subfile);
710
playbin.Suburi = subfile;
271
716
public override void Play ()
273
// HACK, I think that directsoundsink has a bug that resets its volume to 1.0 every time
274
// This seems to fix bgo#641427
276
pipeline.SetState (Gst.State.Playing);
718
playbin.SetState (Gst.State.Playing);
277
719
OnStateChanged (PlayerState.Playing);
280
722
public override void Pause ()
282
pipeline.SetState (Gst.State.Paused);
724
playbin.SetState (Gst.State.Paused);
283
725
OnStateChanged (PlayerState.Paused);
286
728
public override void Close (bool fullShutdown)
288
pipeline.SetState (State.Null);
730
playbin.SetState (State.Null);
289
731
base.Close (fullShutdown);
363
847
public override int SubtitleIndex {
365
if (value >= 0 && value < SubtitleCount) {
849
if (SubtitleCount == 0 || value < -1 || value >= SubtitleCount)
851
int flags = (int)playbin.Flags;
854
flags &= ~(1 << 2);//GST_PLAY_FLAG_TEXT
855
playbin.Flags = (ObjectFlags)flags;
857
flags |= (1 << 2);//GST_PLAY_FLAG_TEXT
858
playbin.Flags = (ObjectFlags)flags;
366
859
playbin.CurrentText = value;
371
864
public override SafeUri SubtitleUri {
372
set { playbin.Suburi = value.AbsoluteUri; }
868
Format format = Format.Bytes;
871
// GStreamer playbin does not support setting the suburi during playback
872
// so we have to stop/play and seek
873
playbin.GetState (out state, 0);
874
paused = (state == State.Paused);
875
if (state >= State.Paused) {
876
playbin.QueryPosition (ref format, out pos);
877
playbin.SetState (State.Ready);
878
// Wait for the state change to complete
879
playbin.GetState (out state, 0);
882
playbin.Suburi = value.AbsoluteUri;
883
playbin.SetState (paused ? State.Paused : State.Playing);
885
// Wait for the state change to complete
886
playbin.GetState (out state, 0);
889
playbin.Seek (format, SeekFlags.Flush | SeekFlags.KeyUnit, pos);
373
892
get { return new SafeUri (playbin.Suburi); }
895
private PreferenceBase replaygain_preference;
897
private void InstallPreferences ()
899
PreferenceService service = ServiceManager.Get<PreferenceService> ();
900
if (service == null) {
904
replaygain_preference = service["general"]["misc"].Add (new SchemaPreference<bool> (ReplayGainEnabledSchema,
905
Catalog.GetString ("_Enable ReplayGain correction"),
906
Catalog.GetString ("For tracks that have ReplayGain data, automatically scale (normalize) playback volume"),
907
delegate { audio_sink.ReplayGainEnabled = ReplayGainEnabledSchema.Get (); }
911
private void UninstallPreferences ()
913
PreferenceService service = ServiceManager.Get<PreferenceService> ();
914
if (service == null) {
918
service["general"]["misc"].Remove (replaygain_preference);
919
replaygain_preference = null;
922
public static readonly SchemaEntry<bool> ReplayGainEnabledSchema = new SchemaEntry<bool> (
923
"player_engine", "replay_gain_enabled",
926
"If ReplayGain data is present on tracks when playing, allow volume scaling"