2
// PlayerEngineService.cs
5
// Aaron Bockover <abockover@novell.com>
7
// Copyright (C) 2006-2008 Novell, Inc.
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
using System.Collections.Generic;
32
using System.Reflection;
39
using Banshee.Streaming;
40
using Banshee.ServiceStack;
41
using Banshee.Sources;
43
using Banshee.Metadata;
44
using Banshee.Configuration;
45
using Banshee.Collection;
46
using Banshee.Equalizer;
47
using Banshee.Collection.Database;
49
namespace Banshee.MediaEngine
51
public delegate bool TrackInterceptHandler (TrackInfo track);
53
public class PlayerEngineService : IInitializeService, IDelayedInitializeService,
54
IRequiredService, IPlayerEngineService, IDisposable
56
private List<PlayerEngine> engines = new List<PlayerEngine> ();
57
private PlayerEngine active_engine;
58
private PlayerEngine default_engine;
59
private PlayerEngine pending_engine;
60
private object pending_playback_for_not_ready;
61
private bool pending_playback_for_not_ready_play;
62
private TrackInfo synthesized_contacting_track;
64
private string preferred_engine_id = null;
66
public event EventHandler PlayWhenIdleRequest;
67
public event TrackInterceptHandler TrackIntercept;
68
public event Action<PlayerEngine> EngineBeforeInitialize;
69
public event Action<PlayerEngine> EngineAfterInitialize;
71
private event DBusPlayerEventHandler dbus_event_changed;
72
event DBusPlayerEventHandler IPlayerEngineService.EventChanged {
73
add { dbus_event_changed += value; }
74
remove { dbus_event_changed -= value; }
77
private event DBusPlayerStateHandler dbus_state_changed;
78
event DBusPlayerStateHandler IPlayerEngineService.StateChanged {
79
add { dbus_state_changed += value; }
80
remove { dbus_state_changed -= value; }
83
public PlayerEngineService ()
87
void IInitializeService.Initialize ()
89
preferred_engine_id = EngineSchema.Get();
91
if (default_engine == null && engines.Count > 0) {
92
default_engine = engines[0];
95
foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes ("/Banshee/MediaEngine/PlayerEngine")) {
99
if (default_engine != null) {
100
active_engine = default_engine;
101
Log.Debug (Catalog.GetString ("Default player engine"), active_engine.Name);
103
default_engine = active_engine;
106
if (default_engine == null || active_engine == null || engines == null || engines.Count == 0) {
107
Log.Warning (Catalog.GetString (
108
"No player engines were found. Please ensure Banshee has been cleanly installed."),
109
"Using the featureless NullPlayerEngine.");
110
PlayerEngine null_engine = new NullPlayerEngine ();
111
LoadEngine (null_engine);
112
active_engine = null_engine;
113
default_engine = null_engine;
116
MetadataService.Instance.HaveResult += OnMetadataServiceHaveResult;
118
TrackInfo.IsPlayingMethod = track => IsPlaying (track) &&
119
track.CacheModelId == CurrentTrack.CacheModelId &&
120
(track.CacheEntryId == null || track.CacheEntryId.Equals (CurrentTrack.CacheEntryId));
123
private void InitializeEngine (PlayerEngine engine)
125
var handler = EngineBeforeInitialize;
126
if (handler != null) {
130
engine.Initialize ();
131
engine.IsInitialized = true;
133
handler = EngineAfterInitialize;
134
if (handler != null) {
139
void IDelayedInitializeService.DelayedInitialize ()
141
foreach (var engine in Engines) {
142
if (engine.DelayedInitialize) {
143
InitializeEngine (engine);
148
private void LoadEngine (TypeExtensionNode node)
150
LoadEngine ((PlayerEngine) node.CreateInstance (typeof (PlayerEngine)));
153
private void LoadEngine (PlayerEngine engine)
155
if (!engine.DelayedInitialize) {
156
InitializeEngine (engine);
159
engine.EventChanged += OnEngineEventChanged;
161
if (engine.Id == preferred_engine_id) {
162
DefaultEngine = engine;
164
if (active_engine == null) {
165
active_engine = engine;
167
engines.Add (engine);
171
public void Dispose ()
173
MetadataService.Instance.HaveResult -= OnMetadataServiceHaveResult;
175
foreach (PlayerEngine engine in engines) {
179
active_engine = null;
180
default_engine = null;
181
pending_engine = null;
183
preferred_engine_id = null;
188
private void OnMetadataServiceHaveResult (object o, MetadataLookupResultArgs args)
190
if (CurrentTrack != null && CurrentTrack.TrackEqual (args.Track as TrackInfo)) {
191
foreach (StreamTag tag in args.ResultTags) {
192
StreamTagger.TrackInfoMerge (CurrentTrack, tag);
195
OnEngineEventChanged (new PlayerEventArgs (PlayerEvent.TrackInfoUpdated));
199
private void HandleStateChange (PlayerEventStateChangeArgs args)
201
if (args.Current == PlayerState.Loaded && CurrentTrack != null) {
202
MetadataService.Instance.Lookup (CurrentTrack);
203
} else if (args.Current == PlayerState.Ready) {
204
// Enable our preferred equalizer if it exists and was enabled last time.
205
if (SupportsEqualizer) {
206
EqualizerManager.Instance.Select ();
209
if (pending_playback_for_not_ready != null) {
210
OpenCheck (pending_playback_for_not_ready, pending_playback_for_not_ready_play);
211
pending_playback_for_not_ready = null;
212
pending_playback_for_not_ready_play = false;
216
DBusPlayerStateHandler dbus_handler = dbus_state_changed;
217
if (dbus_handler != null) {
218
dbus_handler (args.Current.ToString ().ToLower ());
222
private void OnEngineEventChanged (PlayerEventArgs args)
224
if (CurrentTrack != null) {
225
if (args.Event == PlayerEvent.Error
226
&& CurrentTrack.PlaybackError == StreamPlaybackError.None) {
227
CurrentTrack.SavePlaybackError (StreamPlaybackError.Unknown);
228
} else if (args.Event == PlayerEvent.Iterate
229
&& CurrentTrack.PlaybackError != StreamPlaybackError.None) {
230
CurrentTrack.SavePlaybackError (StreamPlaybackError.None);
234
if (args.Event == PlayerEvent.StartOfStream) {
235
incremented_last_played = false;
236
} else if (args.Event == PlayerEvent.EndOfStream) {
237
IncrementLastPlayed ();
242
// Do not raise iterate across DBus to avoid so many calls;
243
// DBus clients should do their own iterating and
244
// event/state checking locally
245
if (args.Event == PlayerEvent.Iterate) {
249
DBusPlayerEventHandler dbus_handler = dbus_event_changed;
250
if (dbus_handler != null) {
251
dbus_handler (args.Event.ToString ().ToLower (),
252
args is PlayerEventErrorArgs ? ((PlayerEventErrorArgs)args).Message : String.Empty,
253
args is PlayerEventBufferingArgs ? ((PlayerEventBufferingArgs)args).Progress : 0
258
private void OnPlayWhenIdleRequest ()
260
EventHandler handler = PlayWhenIdleRequest;
261
if (handler != null) {
262
handler (this, EventArgs.Empty);
266
private bool OnTrackIntercept (TrackInfo track)
268
TrackInterceptHandler handler = TrackIntercept;
269
if (handler == null) {
273
bool handled = false;
275
foreach (TrackInterceptHandler single_handler in handler.GetInvocationList ()) {
276
handled |= single_handler (track);
282
public void Open (TrackInfo track)
284
OpenPlay (track, false);
287
public void Open (SafeUri uri)
289
// Check if the uri exists
290
if (uri == null || !File.Exists (uri.AbsolutePath)) {
295
if (ServiceManager.DbConnection != null) {
296
// Try to find uri in the library
297
int track_id = DatabaseTrackInfo.GetTrackIdForUri (uri);
299
DatabaseTrackInfo track_db = DatabaseTrackInfo.Provider.FetchSingle (track_id);
306
// Not in the library, get info from the file
307
TrackInfo track = new TrackInfo ();
308
using (var file = StreamTagger.ProcessUri (uri)) {
309
StreamTagger.TrackInfoMerge (track, file, false);
315
void IPlayerEngineService.Open (string uri)
317
Open (new SafeUri (uri));
320
public void SetNextTrack (TrackInfo track)
322
if (track != null && EnsureActiveEngineCanPlay (track.Uri)) {
323
active_engine.SetNextTrack (track);
325
active_engine.SetNextTrack ((TrackInfo) null);
329
public void SetNextTrack (SafeUri uri)
331
if (EnsureActiveEngineCanPlay (uri)) {
332
active_engine.SetNextTrack (uri);
334
active_engine.SetNextTrack ((SafeUri) null);
338
private bool EnsureActiveEngineCanPlay (SafeUri uri)
341
// No engine can play the null URI.
344
if (active_engine != FindSupportingEngine (uri)) {
345
if (active_engine.CurrentState == PlayerState.Playing) {
346
// If we're currently playing then we can't switch engines now.
347
// We can't ensure the active engine can play this URI.
350
// If we're not playing, we can switch the active engine to
351
// something that will play this URI.
352
SwitchToEngine (FindSupportingEngine (uri));
360
public void OpenPlay (TrackInfo track)
362
OpenPlay (track, true);
365
private void OpenPlay (TrackInfo track, bool play)
367
if (track == null || !track.CanPlay || OnTrackIntercept (track)) {
372
OpenCheck (track, play);
373
} catch (Exception e) {
375
Log.Error (Catalog.GetString ("Problem with Player Engine"), e.Message, true);
377
ActiveEngine = default_engine;
381
private void OpenCheck (object o, bool play)
383
if (CurrentState == PlayerState.NotReady) {
384
pending_playback_for_not_ready = o;
385
pending_playback_for_not_ready_play = play;
390
TrackInfo track = null;
394
} else if (o is TrackInfo) {
395
track = (TrackInfo)o;
401
if (track != null && (track.MediaAttributes & TrackMediaAttributes.ExternalResource) != 0) {
402
RaiseEvent (new PlayerEventArgs (PlayerEvent.EndOfStream));
406
PlayerEngine supportingEngine = FindSupportingEngine (uri);
407
SwitchToEngine (supportingEngine);
411
active_engine.Open (track);
412
} else if (uri != null) {
413
active_engine.Open (uri);
417
active_engine.Play ();
421
public void IncrementLastPlayed ()
423
// If Length <= 0 assume 100% completion.
424
IncrementLastPlayed (active_engine.Length <= 0
426
: (double)active_engine.Position / active_engine.Length);
429
private bool incremented_last_played = true;
430
public void IncrementLastPlayed (double completed)
432
if (!incremented_last_played && CurrentTrack != null && CurrentTrack.PlaybackError == StreamPlaybackError.None) {
433
CurrentTrack.OnPlaybackFinished (completed);
434
incremented_last_played = true;
438
private PlayerEngine FindSupportingEngine (SafeUri uri)
440
foreach (PlayerEngine engine in engines) {
441
foreach (string extension in engine.ExplicitDecoderCapabilities) {
442
if (!uri.AbsoluteUri.EndsWith (extension)) {
449
foreach (PlayerEngine engine in engines) {
450
foreach (string scheme in engine.SourceCapabilities) {
451
bool supported = scheme == uri.Scheme;
457
// If none of our engines support this URI, return the currently active one.
458
// There doesn't seem to be anything better to do.
459
return active_engine;
462
private bool SwitchToEngine (PlayerEngine switchTo)
464
if (active_engine != switchTo) {
466
pending_engine = switchTo;
467
Log.DebugFormat ("Switching engine to: {0}", switchTo.GetType ());
478
public void Close (bool fullShutdown)
480
IncrementLastPlayed ();
481
active_engine.Close (fullShutdown);
486
if (CurrentState == PlayerState.Idle) {
487
OnPlayWhenIdleRequest ();
489
active_engine.Play ();
498
active_engine.Pause ();
502
public void RestartCurrentTrack ()
504
var track = CurrentTrack;
506
// Don't process the track as played through IncrementLastPlayed, just play it again
507
active_engine.Close (false);
512
// For use by RadioTrackInfo
513
// TODO remove this method once RadioTrackInfo playlist downloading/parsing logic moved here?
514
internal void StartSynthesizeContacting (TrackInfo track)
516
//OnStateChanged (PlayerState.Contacting);
517
RaiseEvent (new PlayerEventStateChangeArgs (CurrentState, PlayerState.Contacting));
518
synthesized_contacting_track = track;
521
internal void EndSynthesizeContacting (TrackInfo track, bool idle)
523
if (track == synthesized_contacting_track) {
524
synthesized_contacting_track = null;
527
RaiseEvent (new PlayerEventStateChangeArgs (PlayerState.Contacting, PlayerState.Idle));
532
public void TogglePlaying ()
534
if (IsPlaying () && CurrentState != PlayerState.Paused) {
536
} else if (CurrentState != PlayerState.NotReady) {
541
public void VideoExpose (IntPtr displayContext, bool direct)
543
active_engine.VideoExpose (displayContext, direct);
546
public void VideoWindowRealize (IntPtr displayContext)
548
active_engine.VideoWindowRealize (displayContext);
551
public IntPtr VideoDisplayContext {
552
set { active_engine.VideoDisplayContext = value; }
553
get { return active_engine.VideoDisplayContext; }
556
public void TrackInfoUpdated ()
558
active_engine.TrackInfoUpdated ();
561
public bool IsPlaying (TrackInfo track)
563
return IsPlaying () && track != null && track.TrackEqual (CurrentTrack);
566
public bool IsPlaying ()
568
return CurrentState == PlayerState.Playing ||
569
CurrentState == PlayerState.Paused ||
570
CurrentState == PlayerState.Loaded ||
571
CurrentState == PlayerState.Loading ||
572
CurrentState == PlayerState.Contacting;
575
public string GetSubtitleDescription (int index)
577
return active_engine.GetSubtitleDescription (index);
580
public void NotifyMouseMove (double x, double y)
582
active_engine.NotifyMouseMove (x, y);
585
public void NotifyMouseButtonPressed (int button, double x, double y)
587
active_engine.NotifyMouseButtonPressed (button, x, y);
590
public void NotifyMouseButtonReleased (int button, double x, double y)
592
active_engine.NotifyMouseButtonReleased (button, x, y);
595
public void NavigateToLeftMenu ()
597
active_engine.NavigateToLeftMenu ();
600
public void NavigateToRightMenu ()
602
active_engine.NavigateToRightMenu ();
605
public void NavigateToUpMenu ()
607
active_engine.NavigateToUpMenu ();
610
public void NavigateToDownMenu ()
612
active_engine.NavigateToDownMenu ();
615
public void NavigateToMenu ()
617
active_engine.NavigateToMenu ();
620
public void ActivateCurrentMenu ()
622
active_engine.ActivateCurrentMenu ();
625
public void GoToNextChapter ()
627
active_engine.GoToNextChapter ();
630
public void GoToPreviousChapter ()
632
active_engine.GoToPreviousChapter ();
635
private void CheckPending ()
637
if (pending_engine != null && pending_engine != active_engine) {
638
if (active_engine.CurrentState == PlayerState.Idle) {
642
active_engine = pending_engine;
643
pending_engine = null;
647
public TrackInfo CurrentTrack {
649
return active_engine == null ? null : active_engine.CurrentTrack ?? synthesized_contacting_track;
653
private Dictionary<string, object> dbus_sucks;
654
IDictionary<string, object> IPlayerEngineService.CurrentTrack {
656
// FIXME: Managed DBus sucks - it explodes if you transport null
657
// or even an empty dictionary (a{sv} in our case). Piece of shit.
658
if (dbus_sucks == null) {
659
dbus_sucks = new Dictionary<string, object> ();
660
dbus_sucks.Add (String.Empty, String.Empty);
663
return CurrentTrack == null ? dbus_sucks : CurrentTrack.GenerateExportable ();
667
public SafeUri CurrentSafeUri {
668
get { return active_engine.CurrentUri; }
671
string IPlayerEngineService.CurrentUri {
672
get { return CurrentSafeUri == null ? String.Empty : CurrentSafeUri.AbsoluteUri; }
675
public PlayerState CurrentState {
676
get { return synthesized_contacting_track != null ? PlayerState.Contacting : active_engine.CurrentState; }
679
string IPlayerEngineService.CurrentState {
680
get { return CurrentState.ToString ().ToLower (); }
683
public PlayerState LastState {
684
get { return active_engine.LastState; }
687
string IPlayerEngineService.LastState {
688
get { return LastState.ToString ().ToLower (); }
691
public ushort Volume {
692
get { return active_engine.Volume; }
694
foreach (PlayerEngine engine in engines) {
695
engine.Volume = value;
700
public uint Position {
701
get { return active_engine.Position; }
702
set { active_engine.Position = value; }
706
get { return (byte)(CurrentTrack == null ? 0 : CurrentTrack.Rating); }
708
if (CurrentTrack != null) {
709
CurrentTrack.Rating = (int)Math.Min (5u, value);
710
CurrentTrack.Save ();
712
foreach (var source in ServiceManager.SourceManager.Sources) {
713
var psource = source as PrimarySource;
714
if (psource != null) {
715
psource.NotifyTracksChanged (BansheeQuery.RatingField);
722
public bool CanSeek {
723
get { return active_engine.CanSeek; }
726
public bool CanPause {
727
get { return CurrentTrack != null && !CurrentTrack.IsLive; }
730
public bool SupportsEqualizer {
731
get { return ((active_engine is IEqualizer) && active_engine.SupportsEqualizer); }
734
public int SubtitleCount {
735
get { return active_engine.SubtitleCount; }
738
public int SubtitleIndex {
739
set { active_engine.SubtitleIndex = value; }
742
public SafeUri SubtitleUri {
743
set { active_engine.SubtitleUri = value; }
744
get { return active_engine.SubtitleUri; }
747
public bool InDvdMenu {
748
get { return active_engine.InDvdMenu; }
751
public VideoDisplayContextType VideoDisplayContextType {
752
get { return active_engine.VideoDisplayContextType; }
757
uint length = active_engine.Length;
760
} else if (CurrentTrack == null) {
764
return (uint) CurrentTrack.Duration.TotalSeconds;
768
public PlayerEngine ActiveEngine {
769
get { return active_engine; }
770
set { pending_engine = value; }
773
public PlayerEngine DefaultEngine {
774
get { return default_engine; }
776
if (engines.Contains (value)) {
777
engines.Remove (value);
780
engines.Insert (0, value);
782
default_engine = value;
783
EngineSchema.Set (value.Id);
787
public IEnumerable<PlayerEngine> Engines {
788
get { return engines; }
791
#region Player Event System
793
private LinkedList<PlayerEventHandlerSlot> event_handlers = new LinkedList<PlayerEventHandlerSlot> ();
795
private struct PlayerEventHandlerSlot
797
public PlayerEvent EventMask;
798
public PlayerEventHandler Handler;
800
public PlayerEventHandlerSlot (PlayerEvent mask, PlayerEventHandler handler)
807
private const PlayerEvent event_all_mask = PlayerEvent.Iterate
808
| PlayerEvent.StateChange
809
| PlayerEvent.StartOfStream
810
| PlayerEvent.EndOfStream
811
| PlayerEvent.Buffering
815
| PlayerEvent.Metadata
816
| PlayerEvent.TrackInfoUpdated
817
| PlayerEvent.RequestNextTrack
818
| PlayerEvent.PrepareVideoWindow;
820
private const PlayerEvent event_default_mask = event_all_mask & ~PlayerEvent.Iterate;
822
private static void VerifyEventMask (PlayerEvent eventMask)
824
if (eventMask <= PlayerEvent.None || eventMask > event_all_mask) {
825
throw new ArgumentOutOfRangeException ("eventMask", "A valid event mask must be provided");
829
public void ConnectEvent (PlayerEventHandler handler)
831
ConnectEvent (handler, event_default_mask, false);
834
public void ConnectEvent (PlayerEventHandler handler, PlayerEvent eventMask)
836
ConnectEvent (handler, eventMask, false);
839
public void ConnectEvent (PlayerEventHandler handler, bool connectAfter)
841
ConnectEvent (handler, event_default_mask, connectAfter);
844
public void ConnectEvent (PlayerEventHandler handler, PlayerEvent eventMask, bool connectAfter)
846
lock (event_handlers) {
847
VerifyEventMask (eventMask);
849
PlayerEventHandlerSlot slot = new PlayerEventHandlerSlot (eventMask, handler);
852
event_handlers.AddLast (slot);
854
event_handlers.AddFirst (slot);
859
private LinkedListNode<PlayerEventHandlerSlot> FindEventNode (PlayerEventHandler handler)
861
LinkedListNode<PlayerEventHandlerSlot> node = event_handlers.First;
862
while (node != null) {
863
if (node.Value.Handler == handler) {
872
public void DisconnectEvent (PlayerEventHandler handler)
874
lock (event_handlers) {
875
LinkedListNode<PlayerEventHandlerSlot> node = FindEventNode (handler);
877
event_handlers.Remove (node);
882
public void ModifyEvent (PlayerEvent eventMask, PlayerEventHandler handler)
884
lock (event_handlers) {
885
VerifyEventMask (eventMask);
887
LinkedListNode<PlayerEventHandlerSlot> node = FindEventNode (handler);
889
PlayerEventHandlerSlot slot = node.Value;
890
slot.EventMask = eventMask;
896
private void RaiseEvent (PlayerEventArgs args)
898
lock (event_handlers) {
899
if (args.Event == PlayerEvent.StateChange && args is PlayerEventStateChangeArgs) {
900
HandleStateChange ((PlayerEventStateChangeArgs)args);
903
LinkedListNode<PlayerEventHandlerSlot> node = event_handlers.First;
904
while (node != null) {
905
if ((node.Value.EventMask & args.Event) == args.Event) {
907
node.Value.Handler (args);
908
} catch (Exception e) {
909
Log.Exception (String.Format ("Error running PlayerEngine handler for {0}", args.Event), e);
919
string IService.ServiceName {
920
get { return "PlayerEngine"; }
923
IDBusExportable IDBusExportable.Parent {
927
public static readonly SchemaEntry<int> VolumeSchema = new SchemaEntry<int> (
928
"player_engine", "volume",
931
"Volume of playback relative to mixer output"
934
public static readonly SchemaEntry<string> EngineSchema = new SchemaEntry<string> (
935
"player_engine", "backend",
938
"Name of media playback engine backend"