1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
21 #include "mediaelement.h"
24 #include "mediaplayer.h"
27 * PlaylistParserInternal
30 PlaylistParserInternal::PlaylistParserInternal ()
32 parser
= XML_ParserCreate (NULL
);
37 PlaylistParserInternal::~PlaylistParserInternal ()
39 XML_ParserFree (parser
);
47 PlaylistNode::PlaylistNode (PlaylistEntry
*entry
) : List::Node ()
54 PlaylistNode::~PlaylistNode ()
66 PlaylistEntry::PlaylistEntry (Playlist
*parent
)
67 : EventObject (Type::PLAYLISTENTRY
)
69 LOG_PLAYLIST ("PlaylistEntry::PlaylistEntry (%p)\n", parent
);
72 g_return_if_fail (parent
!= NULL
); // should ge a g_warn..., but glib 2.10 doesn't have g_warn.
75 PlaylistEntry::PlaylistEntry (Type::Kind kind
, Playlist
*parent
)
78 LOG_PLAYLIST ("PlaylistEntry::PlaylistEntry (%p)\n", parent
);
81 g_return_if_fail (parent
!= NULL
); // should ge a g_warn..., but glib 2.10 doesn't have g_warn.
85 PlaylistEntry::PlaylistEntry (Type::Kind kind
)
88 LOG_PLAYLIST ("PlaylistEntry::PlaylistEntry ()\n");
94 PlaylistEntry::Dispose ()
96 LOG_PLAYLIST ("PlaylistEntry::Dispose () id: %i media: %i\n", GET_OBJ_ID (this), GET_OBJ_ID (media
));
101 tmp
->RemoveSafeHandlers (this);
102 tmp
->DisposeObject (tmp
);
108 g_free (full_source_name
);
109 full_source_name
= NULL
;
121 g_free (info_target
);
127 if (params
!= NULL
) {
128 g_hash_table_destroy (params
);
132 EventObject::Dispose ();
136 PlaylistEntry::Init (Playlist
*parent
)
138 // Parent might be null
139 this->parent
= parent
;
142 full_source_name
= NULL
;
145 play_when_available
= false;
155 set_values
= (PlaylistKind::Kind
) 0;
161 PlaylistEntry::Initialize (Media
*media
)
163 g_return_if_fail (media
!= NULL
);
164 g_return_if_fail (this->media
== NULL
);
166 media
->AddSafeHandler (Media::OpenCompletedEvent
, OpenCompletedCallback
, this);
167 media
->AddSafeHandler (Media::OpeningEvent
, OpeningCallback
, this);
168 media
->AddSafeHandler (Media::SeekingEvent
, SeekingCallback
, this);
169 media
->AddSafeHandler (Media::SeekCompletedEvent
, SeekCompletedCallback
, this);
170 media
->AddSafeHandler (Media::CurrentStateChangedEvent
, CurrentStateChangedCallback
, this);
171 media
->AddSafeHandler (Media::DownloadProgressChangedEvent
, DownloadProgressChangedCallback
, this);
172 media
->AddSafeHandler (Media::BufferingProgressChangedEvent
, BufferingProgressChangedCallback
, this);
173 media
->AddSafeHandler (Media::MediaErrorEvent
, MediaErrorCallback
, this);
180 PlaylistEntry::InitializeWithStream (ManagedStreamCallbacks
*callbacks
)
183 ManagedStreamSource
*source
;
184 PlaylistRoot
*root
= GetRoot ();
186 g_return_if_fail (callbacks
!= NULL
);
187 g_return_if_fail (root
!= NULL
);
189 media
= new Media (root
);
192 source
= new ManagedStreamSource (media
, callbacks
);
193 media
->Initialize (source
);
194 if (!media
->HasReportedError ())
201 PlaylistEntry::InitializeWithSource (IMediaSource
*source
)
204 PlaylistRoot
*root
= GetRoot ();
206 g_return_if_fail (source
!= NULL
);
207 g_return_if_fail (root
!= NULL
);
209 media
= source
->GetMediaReffed ();
211 g_return_if_fail (media
!= NULL
);
215 media
->Initialize (source
);
216 if (!media
->HasReportedError ())
222 PlaylistEntry::InitializeWithUri (const char *uri
)
225 PlaylistRoot
*root
= GetRoot ();
227 g_return_if_fail (uri
!= NULL
);
228 g_return_if_fail (root
!= NULL
);
230 media
= new Media (root
);
232 media
->Initialize (uri
);
233 if (!media
->HasReportedError ())
239 PlaylistEntry::InitializeWithDownloader (Downloader
*dl
, const char *PartName
)
242 PlaylistRoot
*root
= GetRoot ();
244 g_return_if_fail (dl
!= NULL
);
245 g_return_if_fail (root
!= NULL
);
247 media
= new Media (root
);
249 media
->Initialize (dl
, PartName
);
250 if (!media
->HasReportedError ())
256 PlaylistEntry::InitializeWithDemuxer (IMediaDemuxer
*demuxer
)
259 PlaylistRoot
*root
= GetRoot ();
261 g_return_if_fail (demuxer
!= NULL
);
262 g_return_if_fail (root
!= NULL
);
264 media
= demuxer
->GetMediaReffed ();
266 g_return_if_fail (media
!= NULL
);
269 media
->Initialize (demuxer
);
270 if (!media
->HasReportedError ())
276 PlaylistEntry::OpeningHandler (Media
*media
, EventArgs
*args
)
278 PlaylistRoot
*root
= GetRoot ();
280 LOG_PLAYLIST ("PlaylistEntry::OpeningHandler (%p, %p)\n", media
, args
);
282 g_return_if_fail (root
!= NULL
);
284 root
->Emit (PlaylistRoot::OpeningEvent
, args
);
288 PlaylistEntry::OpenMediaPlayer ()
290 PlaylistRoot
*root
= GetRoot ();
291 MediaPlayer
*mplayer
;
293 g_return_if_fail (opened
== true);
294 g_return_if_fail (root
!= NULL
);
296 mplayer
= GetMediaPlayer ();
297 g_return_if_fail (mplayer
!= NULL
);
299 mplayer
->Open (media
, this);
301 root
->Emit (PlaylistRoot::OpenCompletedEvent
, NULL
);
305 PlaylistEntry::OpenCompletedHandler (Media
*media
, EventArgs
*args
)
307 PlaylistRoot
*root
= GetRoot ();
308 IMediaDemuxer
*demuxer
;
311 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p)\n", media
, args
);
314 g_return_if_fail (media
!= NULL
);
315 g_return_if_fail (root
!= NULL
);
316 g_return_if_fail (parent
!= NULL
);
318 demuxer
= media
->GetDemuxer ();
320 g_return_if_fail (demuxer
!= NULL
);
322 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p) demuxer: %i %s\n", media
, args
, GET_OBJ_ID (demuxer
), demuxer
->GetTypeName ());
324 if (demuxer
->IsPlaylist ()) {
325 playlist
= demuxer
->GetPlaylist ();
327 g_return_if_fail (playlist
!= NULL
);
328 g_return_if_fail (parent
!= NULL
);
330 parent
->ReplaceCurrentEntry (playlist
);
333 if (parent
->GetCurrentEntry () == this) {
336 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p): opened entry in advance, waiting for current entry to finish.\n", media
, args
);
343 PlaylistEntry::SeekingHandler (Media
*media
, EventArgs
*args
)
345 LOG_PLAYLIST ("PlaylistEntry::SeekingHandler (%p, %p)\n", media
, args
);
349 PlaylistEntry::SeekCompletedHandler (Media
*media
, EventArgs
*args
)
351 PlaylistRoot
*root
= GetRoot ();
353 LOG_PLAYLIST ("PlaylistEntry::SeekCompletedHandler (%p, %p)\n", media
, args
);
355 g_return_if_fail (root
!= NULL
);
359 root
->Emit (PlaylistRoot::SeekCompletedEvent
, args
);
363 PlaylistEntry::CurrentStateChangedHandler (Media
*media
, EventArgs
*args
)
365 LOG_PLAYLIST ("PlaylistEntry::CurrentStateChangedHandler (%p, %p)\n", media
, args
);
369 PlaylistEntry::MediaErrorHandler (Media
*media
, ErrorEventArgs
*args
)
371 LOG_PLAYLIST ("PlaylistEntry::MediaErrorHandler (%p, %p): %s '%s'\n", media
, args
, GetFullSourceName (), args
? args
->error_message
: "?");
373 g_return_if_fail (parent
!= NULL
);
375 parent
->OnEntryFailed (args
);
379 PlaylistEntry::DownloadProgressChangedHandler (Media
*media
, EventArgs
*args
)
383 LOG_PLAYLIST ("PlaylistEntry::DownloadProgressChanged (%p, %p %.2f). Disposed: %i\n", media
, args
, args
? ((ProgressEventArgs
*) args
)->progress
: -1.0, IsDisposed ());
390 g_return_if_fail (root
!= NULL
);
394 root
->Emit (PlaylistRoot::DownloadProgressChangedEvent
, args
);
398 PlaylistEntry::BufferingProgressChangedHandler (Media
*media
, EventArgs
*args
)
400 PlaylistRoot
*root
= GetRoot ();
402 LOG_PLAYLIST ("PlaylistEntry::BufferingProgressChanged (%p, %p) %.2f\n", media
, args
, args
? ((ProgressEventArgs
*) args
)->progress
: -1.0);
405 return; // this might happen if the media is still buffering and we're in the process of getting cleaned up
409 root
->Emit (PlaylistRoot::BufferingProgressChangedEvent
, args
);
413 PlaylistEntry::Seek (guint64 pts
)
415 LOG_PLAYLIST ("PlaylistEntry::Seek (%" G_GUINT64_FORMAT
")\n", pts
);
417 g_return_if_fail (media
!= NULL
);
419 media
->SeekAsync (pts
);
423 PlaylistEntry::AddParams (const char *name
, const char *value
)
425 char *uppername
= g_ascii_strup (name
, strlen (name
));
426 if (!strcmp (uppername
, "AUTHOR")) {
428 } else if (!strcmp (uppername
, "ABSTRACT")) {
430 } else if (!strcmp (uppername
, "TITLE")) {
432 } else if (!strcmp (uppername
, "COPYRIGHT")) {
433 SetCopyright (value
);
434 } else if (!strcmp (uppername
, "INFOTARGET")) {
435 SetInfoTarget (value
);
436 } else if (!strcmp (uppername
, "INFOURL")) {
440 params
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, g_free
);
442 if (g_hash_table_lookup (params
, uppername
) == NULL
) {
443 g_hash_table_insert (params
, uppername
, g_strdup (value
));
451 PlaylistEntry::GetBase ()
457 PlaylistEntry::GetBaseInherited ()
462 return parent
->GetBaseInherited ();
467 PlaylistEntry::SetBase (Uri
*base
)
469 // TODO: Haven't been able to make BASE work with SL,
470 // which means that I haven't been able to confirm any behaviour.
471 if (!(set_values
& PlaylistKind::Base
)) {
473 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Base
);
480 PlaylistEntry::GetTitle ()
486 PlaylistEntry::SetTitle (const char *title
)
488 if (!(set_values
& PlaylistKind::Title
)) {
489 this->title
= g_strdup (title
);
490 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Title
);
495 PlaylistEntry::GetAuthor ()
500 void PlaylistEntry::SetAuthor (const char *author
)
502 if (!(set_values
& PlaylistKind::Author
)) {
503 this->author
= g_strdup (author
);
504 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Author
);
509 PlaylistEntry::GetAbstract ()
515 PlaylistEntry::SetAbstract (const char *abstract
)
517 if (!(set_values
& PlaylistKind::Abstract
)) {
518 this->abstract
= g_strdup (abstract
);
519 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Abstract
);
524 PlaylistEntry::GetCopyright ()
530 PlaylistEntry::SetCopyright (const char *copyright
)
532 if (!(set_values
& PlaylistKind::Copyright
)) {
533 this->copyright
= g_strdup (copyright
);
534 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Copyright
);
539 PlaylistEntry::GetSourceName ()
545 PlaylistEntry::SetSourceName (Uri
*source_name
)
547 if (this->source_name
)
548 delete this->source_name
;
549 this->source_name
= source_name
;
553 PlaylistEntry::GetStartTime ()
559 PlaylistEntry::SetStartTime (TimeSpan start_time
)
561 if (!(set_values
& PlaylistKind::StartTime
)) {
562 this->start_time
= start_time
;
563 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::StartTime
);
568 PlaylistEntry::GetDuration ()
574 PlaylistEntry::GetInheritedDuration ()
576 if (HasDuration ()) {
577 return GetDuration ();
578 } else if (parent
!= NULL
) {
579 return parent
->GetInheritedDuration ();
586 PlaylistEntry::HasInheritedDuration ()
588 if (HasDuration ()) {
590 } else if (parent
!= NULL
) {
591 return parent
->HasInheritedDuration ();
598 PlaylistEntry::SetDuration (Duration
*duration
)
600 if (!(set_values
& PlaylistKind::Duration
)) {
601 this->duration
= duration
;
602 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Duration
);
607 PlaylistEntry::GetInfoTarget ()
613 PlaylistEntry::SetInfoTarget (const char *info_target
)
615 g_free (this->info_target
);
616 this->info_target
= g_strdup (info_target
);
620 PlaylistEntry::GetInfoURL ()
626 PlaylistEntry::SetInfoURL (const char *info_url
)
628 g_free (this->info_url
);
629 this->info_url
= g_strdup (info_url
);
633 PlaylistEntry::GetClientSkip ()
639 PlaylistEntry::SetClientSkip (bool value
)
645 PlaylistEntry::GetElement ()
647 g_return_val_if_fail (parent
!= NULL
, NULL
);
649 return parent
->GetElement ();
653 PlaylistEntry::ClearMedia ()
655 g_return_if_fail (media
!= NULL
);
661 PlaylistEntry::GetMediaPlayer ()
663 PlaylistRoot
*root
= GetRoot ();
665 g_return_val_if_fail (root
!= NULL
, NULL
);
667 return root
->GetMediaPlayer ();
671 add_attribute (MediaAttributeCollection
*attributes
, const char *name
, const char *attr
)
676 MediaAttribute
*attribute
= new MediaAttribute ();
677 attribute
->SetValue (attr
);
678 attribute
->SetName (name
);
680 attributes
->Add (attribute
);
685 add_attribute_glib (const char *name
, const char *value
, MediaAttributeCollection
*attributes
)
687 add_attribute (attributes
, name
, value
);
691 PlaylistEntry::PopulateMediaAttributes ()
693 LOG_PLAYLIST ("PlaylistEntry::PopulateMediaAttributes ()\n");
695 const char *abstract
= NULL
;
696 const char *author
= NULL
;
697 const char *copyright
= NULL
;
698 const char *title
= NULL
;
699 const char *infotarget
= NULL
;
700 const char *infourl
= NULL
;
701 const char *baseurl
= NULL
;
703 MediaElement
*element
= GetElement ();
704 PlaylistEntry
*current
= this;
705 MediaAttributeCollection
*attributes
;
707 g_return_if_fail (element
!= NULL
);
709 if (!(attributes
= element
->GetAttributes ())) {
710 attributes
= new MediaAttributeCollection ();
711 element
->SetAttributes (attributes
);
713 attributes
->Clear ();
716 while (current
!= NULL
) {
717 if (abstract
== NULL
)
718 abstract
= current
->GetAbstract ();
720 author
= current
->GetAuthor ();
721 if (copyright
== NULL
)
722 copyright
= current
->GetCopyright ();
724 title
= current
->GetTitle ();
725 if (infotarget
== NULL
)
726 infotarget
= current
->GetInfoTarget ();
728 infourl
= current
->GetInfoURL ();
729 if (baseurl
== NULL
&& current
->GetBase () != NULL
)
730 baseurl
= current
->GetBase ()->originalString
;
732 current
= current
->GetParent ();
735 add_attribute (attributes
, "ABSTRACT", abstract
);
736 add_attribute (attributes
, "AUTHOR", author
);
737 add_attribute (attributes
, "BaseURL", baseurl
);
738 add_attribute (attributes
, "COPYRIGHT", copyright
);
739 add_attribute (attributes
, "InfoTarget", infotarget
);
740 add_attribute (attributes
, "InfoURL", infourl
);
741 add_attribute (attributes
, "TITLE", title
);
744 while (current
!= NULL
) {
745 if (current
->params
!= NULL
)
746 g_hash_table_foreach (current
->params
, (GHFunc
) add_attribute_glib
, attributes
);
747 current
= current
->GetParent ();
752 PlaylistEntry::GetFullSourceName ()
755 * Now here we have some interesting semantics:
756 * - BASE has to be a complete url, with scheme and domain
757 * - BASE only matters up to the latest / (if no /, the entire BASE is used)
759 * Examples (numbered according to the test-playlist-with-base test in test/media/video)
761 * 01 localhost/dir/ + * = error
762 * 02 /dir/ + * = error
764 * 04 http://localhost/dir/ + somefile = http://localhost/dir/somefile
765 * 05 http://localhost/dir + somefile = http://localhost/somefile
766 * 06 http://localhost + somefile = http://localhost/somefile
767 * 07 http://localhost/dir/ + /somefile = http://localhost/somefile
768 * 08 http://localhost/dir/ + dir2/somefile = http://localhost/dir/dir2/somefile
769 * 09 rtsp://localhost/ + somefile = http://localhost/somefile
770 * 10 mms://localhost/dir/ + somefile = mms://localhost/dir/somefile
771 * 11 http://localhost/?huh + somefile = http://localhost/somefile
772 * 12 http://localhost/#huh + somefile = http://localhost/somefile
773 * 13 httP://localhost/ + somefile = http://localhost/somefile
777 // TODO: url validation, however it should probably happen inside MediaElement when we set the source
779 if (full_source_name
== NULL
) {
780 Uri
*base
= GetBaseInherited ();
781 Uri
*current
= GetSourceName ();
786 //printf ("PlaylistEntry::GetFullSourceName (), base: %s, current: %s\n", base ? base->ToString () : "NULL", current ? current->ToString () : "NULL");
788 if (current
== NULL
) {
790 } else if (current
->GetHost () != NULL
) {
791 //printf (" current host (%s) is something, scheme: %s\n", current->GetHost (), current->scheme);
793 } else if (base
!= NULL
) {
795 result
->scheme
= g_strdup (base
->GetScheme());
796 result
->user
= g_strdup (base
->GetUser());
797 result
->passwd
= g_strdup (base
->GetPasswd());
798 result
->host
= g_strdup (base
->GetHost());
799 result
->port
= base
->GetPort();
800 // we ignore the params, query and fragment values.
801 if (current
->GetPath() != NULL
&& current
->GetPath() [0] == '/') {
802 //printf (" current path is relative to root dir on host\n");
803 result
->path
= g_strdup (current
->GetPath());
804 } else if (base
->GetPath() == NULL
) {
805 //printf (" base path is root dir on host\n");
806 result
->path
= g_strdup (current
->GetPath());
808 pathsep
= strrchr (base
->GetPath(), '/');
809 if (pathsep
!= NULL
) {
810 if ((size_t) (pathsep
- base
->GetPath() + 1) == strlen (base
->GetPath())) {
811 //printf (" last character of base path (%s) is /\n", base->path);
812 result
->path
= g_strjoin (NULL
, base
->GetPath(), current
->GetPath(), NULL
);
814 //printf (" base path (%s) does not end with /, only copy path up to the last /\n", base->path);
815 base_path
= g_strndup (base
->GetPath(), pathsep
- base
->GetPath() + 1);
816 result
->path
= g_strjoin (NULL
, base_path
, current
->GetPath(), NULL
);
820 //printf (" base path (%s) does not contain a /\n", base->path);
821 result
->path
= g_strjoin (NULL
, base
->GetPath(), "/", current
->GetPath(), NULL
);
825 //printf (" there's no base\n");
829 full_source_name
= result
->ToString ();
831 //printf (" result: %s\n", full_source_name);
833 if (result
!= base
&& result
!= current
)
836 return full_source_name
;
840 PlaylistEntry::Open ()
842 LOG_PLAYLIST ("PlaylistEntry::Open (), media = %p, FullSourceName = %s\n", media
, GetFullSourceName ());
845 g_return_if_fail (GetFullSourceName () != NULL
);
846 InitializeWithUri (GetFullSourceName ());
855 PlaylistEntry::Play ()
857 MediaPlayer
*mplayer
= GetMediaPlayer ();
858 PlaylistRoot
*root
= GetRoot ();
860 LOG_PLAYLIST ("PlaylistEntry::Play (), play_when_available: %s, media: %p, source name: %s\n", play_when_available
? "true" : "false", media
, source_name
? source_name
->ToString () : "NULL");
862 g_return_if_fail (media
!= NULL
);
863 g_return_if_fail (mplayer
!= NULL
);
864 g_return_if_fail (root
!= NULL
);
868 root
->Emit (PlaylistRoot::PlayEvent
);
872 PlaylistEntry::Pause ()
874 MediaPlayer
*mplayer
= GetMediaPlayer ();
875 PlaylistRoot
*root
= GetRoot ();
877 LOG_PLAYLIST ("PlaylistEntry::Pause ()\n");
879 g_return_if_fail (media
!= NULL
);
880 g_return_if_fail (mplayer
!= NULL
);
881 g_return_if_fail (root
!= NULL
);
883 play_when_available
= false;
884 media
->PauseAsync ();
887 root
->Emit (PlaylistRoot::PauseEvent
);
891 PlaylistEntry::Stop ()
893 LOG_PLAYLIST ("PlaylistEntry::Stop ()\n");
895 play_when_available
= false;
901 PlaylistEntry::GetMedia ()
907 PlaylistEntry::IsSingleFile ()
909 return parent
? parent
->IsSingleFile () : false;
913 PlaylistEntry::GetRoot ()
920 if (parent
== NULL
) {
921 g_return_val_if_fail (GetObjectType () == Type::PLAYLISTROOT
, NULL
);
922 return (PlaylistRoot
*) this;
927 while (pl
->parent
!= NULL
)
930 g_return_val_if_fail (pl
->GetObjectType () == Type::PLAYLISTROOT
, NULL
);
932 return (PlaylistRoot
*) pl
;
939 Playlist::Playlist (Playlist
*parent
, IMediaSource
*source
)
940 : PlaylistEntry (Type::PLAYLIST
, parent
)
942 is_single_file
= false;
946 this->source
= source
;
947 this->source
->ref ();
950 Playlist::Playlist (Type::Kind kind
)
951 : PlaylistEntry (kind
)
953 LOG_PLAYLIST ("Playlist::Playlist ()\n");
954 is_single_file
= true;
957 AddEntry (new PlaylistEntry (this));
963 LOG_PLAYLIST ("Playlist::Init ()\n");
965 entries
= new List ();
974 PlaylistEntry
*entry
;
976 LOG_PLAYLIST ("Playlist::Dispose () id: %i\n", GET_OBJ_ID (this));
980 if (entries
!= NULL
) {
981 node
= (PlaylistNode
*) entries
->First ();
982 while (node
!= NULL
) {
983 entry
= node
->GetEntry ();
986 node
= (PlaylistNode
*) node
->next
;
997 PlaylistEntry::Dispose ();
1002 Playlist::IsCurrentEntryLastEntry ()
1004 PlaylistEntry
*entry
;
1007 if (entries
->Last () == NULL
)
1010 if (current_node
!= entries
->Last ())
1013 entry
= GetCurrentEntry ();
1015 if (!entry
->IsPlaylist ())
1018 pl
= (Playlist
*) entry
;
1020 return pl
->IsCurrentEntryLastEntry ();
1026 PlaylistEntry
*current_entry
;
1028 LOG_PLAYLIST ("Playlist::Open ()\n");
1030 current_node
= (PlaylistNode
*) entries
->First ();
1032 current_entry
= GetCurrentEntry ();
1034 while (current_entry
&& current_entry
->HasDuration () && current_entry
->GetDuration ()->HasTimeSpan() &&
1035 current_entry
->GetDuration ()->GetTimeSpan () == 0) {
1036 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry
->GetSourceName ()->ToString ());
1037 current_node
= (PlaylistNode
*) current_node
->next
;
1038 current_entry
= GetCurrentEntry ();
1042 current_entry
->Open ();
1046 LOG_PLAYLIST ("Playlist::Open (): current node: %p, current entry: %p\n", current_entry
, GetCurrentEntry ());
1050 Playlist::PlayNext ()
1052 PlaylistEntry
*current_entry
;
1053 MediaElement
*element
= GetElement ();
1054 PlaylistRoot
*root
= GetRoot ();
1056 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p\n", current_node
);
1057 g_return_val_if_fail (root
!= NULL
, false);
1064 current_entry
= GetCurrentEntry ();
1066 if (current_entry
->HasDuration() && current_entry
->GetDuration()->IsForever ()) {
1067 element
->SetPlayRequested ();
1068 current_entry
->Play ();
1072 if (current_entry
->IsPlaylist ()) {
1073 Playlist
*current_playlist
= (Playlist
*) current_entry
;
1074 if (current_playlist
->PlayNext ())
1078 if (current_node
->next
) {
1079 current_node
= (PlaylistNode
*) current_node
->next
;
1081 current_entry
= GetCurrentEntry ();
1082 if (current_entry
) {
1083 LOG_PLAYLIST ("Playlist::PlayNext () playing entry: %p %s\n", current_entry
, current_entry
->GetFullSourceName ());
1084 element
->SetPlayRequested ();
1085 root
->Emit (PlaylistRoot::EntryChangedEvent
);
1086 current_entry
->Open ();
1091 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p, nothing to play (is root: %i)\n", current_node
, GetObjectType () == Type::PLAYLISTROOT
);
1093 if (GetObjectType () == Type::PLAYLISTROOT
)
1094 root
->Emit (PlaylistRoot::MediaEndedEvent
);
1100 Playlist::OnEntryEnded ()
1102 LOG_PLAYLIST ("Playlist::OnEntryEnded ()\n");
1107 Playlist::OnEntryFailed (ErrorEventArgs
*args
)
1110 PlaylistRoot
*root
= GetRoot ();
1112 LOG_PLAYLIST ("Playlist::OnEntryFailed () extended_code: %i is_single_file: %i\n", args
? args
->extended_code
: 0, is_single_file
);
1114 g_return_if_fail (root
!= NULL
);
1116 // media or playlist 404: fatal
1117 // invalid playlist (playlist parsing failed): fatal
1118 // invalid media (gif, swf): play next
1122 // check if we're in a playlist
1123 if (GetMedia () != NULL
&& GetMedia ()->GetDemuxer () != NULL
&& GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER
) {
1125 if (args
->extended_code
== MEDIA_UNKNOWN_CODEC
) {
1131 // we're not a playlist
1139 root
->Emit (PlaylistRoot::MediaErrorEvent
, args
);
1146 Playlist::Seek (guint64 pts
)
1148 PlaylistEntry
*current_entry
;
1150 LOG_PLAYLIST ("Playlist::Seek (%llu)\n", pts
);
1152 current_entry
= GetCurrentEntry ();
1154 g_return_if_fail (current_entry
!= NULL
);
1156 current_entry
->Seek (pts
);
1162 PlaylistEntry
*current_entry
;
1164 LOG_PLAYLIST ("Playlist::Play ()\n");
1166 current_entry
= GetCurrentEntry ();
1168 g_return_if_fail (current_entry
!= NULL
);
1170 if (current_entry
&& current_entry
->HasDuration () && current_entry
->GetDuration () == 0) {
1171 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry
->GetSourceName ()->ToString ());
1175 current_entry
->Play ();
1182 PlaylistEntry
*current_entry
;
1184 LOG_PLAYLIST ("Playlist::Pause ()\n");
1186 current_entry
= GetCurrentEntry ();
1188 g_return_if_fail (current_entry
!= NULL
);
1190 current_entry
->Pause ();
1198 LOG_PLAYLIST ("Playlist::Stop ()\n");
1200 node
= (PlaylistNode
*) entries
->First ();
1201 current_node
= node
; // reset to first node
1202 while (node
!= NULL
) {
1203 node
->GetEntry ()->Stop ();
1204 node
= (PlaylistNode
*) node
->next
;
1209 Playlist::PopulateMediaAttributes ()
1211 PlaylistEntry
*current_entry
= GetCurrentEntry ();
1213 LOG_PLAYLIST ("Playlist::PopulateMediaAttributes ()\n");
1218 current_entry
->PopulateMediaAttributes ();
1222 Playlist::AddEntry (PlaylistEntry
*entry
)
1226 LOG_PLAYLIST ("Playlist::AddEntry (%p) Count: %i\n", entry
, entries
->Length ());
1228 node
= new PlaylistNode (entry
);
1229 entries
->Append (node
);
1232 if (entries
->Length () == 1) {
1233 g_return_if_fail (current_node
== NULL
);
1234 current_node
= node
;
1239 Playlist::ReplaceCurrentEntry (Playlist
*pl
)
1243 PlaylistEntry
*current_entry
= GetCurrentEntry ();
1245 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntry (%p)\n", pl
);
1247 // check for too nested playlist
1249 PlaylistEntry
*e
= this;
1250 while (e
!= NULL
&& e
->IsPlaylist ()) {
1251 if (e
->GetObjectType () != Type::PLAYLISTROOT
&& e
->GetMedia () != NULL
&& e
->GetMedia ()->GetDemuxer () != NULL
&& e
->GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER
)
1253 e
= e
->GetParent ();
1256 ErrorEventArgs
*args
= new ErrorEventArgs (MediaError
, 4001, "AG_E_NETWORK_ERROR");
1257 OnEntryFailed (args
);
1263 if (current_entry
->IsPlaylist ()) {
1264 result
= ((Playlist
*) current_entry
)->ReplaceCurrentEntry (pl
);
1266 PlaylistNode
*pln
= new PlaylistNode (pl
);
1267 pl
->MergeWith (current_entry
);
1268 entries
->InsertBefore (pln
, current_node
);
1269 entries
->Remove (current_node
);
1270 pl
->SetParent (this);
1275 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntrY (%p) [DONE]\n", pl
);
1281 Playlist::MergeWith (PlaylistEntry
*entry
)
1283 LOG_PLAYLIST ("Playlist::MergeWith (%p)\n", entry
);
1285 SetBase (entry
->GetBase () ? new Uri (*entry
->GetBase ()) : NULL
);
1286 SetTitle (entry
->GetTitle ());
1287 SetAuthor (entry
->GetAuthor ());
1288 SetAbstract (entry
->GetAbstract ());
1289 SetCopyright (entry
->GetCopyright ());
1291 SetSourceName (entry
->GetSourceName () ? new Uri (*entry
->GetSourceName ()) : NULL
);
1292 if (entry
->HasDuration ())
1293 SetDuration (entry
->GetDuration ());
1294 Initialize (entry
->GetMedia ());
1295 entry
->ClearMedia ();
1299 Playlist::GetCurrentPlaylistEntry ()
1301 PlaylistEntry
*result
= NULL
;
1304 result
= current_node
->GetEntry () ->GetCurrentPlaylistEntry ();
1312 PlaylistRoot::PlaylistRoot (MediaElement
*element
)
1313 : Playlist (Type::PLAYLISTROOT
)
1315 this->element
= element
;
1318 mplayer
= element
->GetMediaPlayer ();
1319 mplayer
->AddHandler (MediaPlayer::MediaEndedEvent
, MediaEndedCallback
, this);
1320 mplayer
->AddHandler (MediaPlayer::BufferUnderflowEvent
, BufferUnderflowCallback
, this);
1325 PlaylistRoot::Dispose ()
1327 if (mplayer
!= NULL
) {
1328 mplayer
->RemoveAllHandlers (this);
1333 Playlist::Dispose ();
1337 PlaylistRoot::IsSingleFile ()
1339 PlaylistEntry
*entry
;
1341 if (GetCount () != 1)
1344 entry
= GetCurrentEntry ();
1348 if (entry
->GetObjectType () == Type::PLAYLISTENTRY
)
1351 return entry
->IsSingleFile ();
1356 PlaylistEntry::DumpInternal (int tabs
)
1358 printf ("%*s%s %i\n", tabs
, "", GetTypeName (), GET_OBJ_ID (this));
1360 printf ("%*sParent: %p %s\n", tabs
, "", parent
, parent
? parent
->GetTypeName () : NULL
);
1361 printf ("%*sFullSourceName: %s\n", tabs
, "", GetFullSourceName ());
1362 printf ("%*sDuration: %s %.2f seconds\n", tabs
, "", HasDuration () ? "yes" : "no", HasDuration () ? GetDuration ()->ToSecondsFloat () : 0.0);
1363 printf ("%*sMedia: %i %s\n", tabs
, "", GET_OBJ_ID (media
), media
? "" : "(null)");
1365 printf ("%*sUri: %s\n", tabs
, "", media
->GetUri ());
1366 printf ("%*sDemuxer: %i %s\n", tabs
, "", GET_OBJ_ID (media
->GetDemuxer ()), media
->GetDemuxer () ? media
->GetDemuxer ()->GetTypeName () : "N/A");
1367 printf ("%*sSource: %i %s\n", tabs
, "", GET_OBJ_ID (media
->GetSource ()), media
->GetSource () ? media
->GetSource ()->GetTypeName () : "N/A");
1373 Playlist::DumpInternal (int tabs
)
1377 PlaylistEntry::DumpInternal (tabs
);
1378 printf ("%*s %i entries:\n", tabs
, "", entries
->Length ());
1379 node
= (PlaylistNode
*) entries
->First ();
1380 while (node
!= NULL
) {
1381 if (node
== current_node
)
1382 printf ("*%*s * CURRENT NODE *\n", tabs
, "");
1383 node
->GetEntry ()->DumpInternal (tabs
+ 2);
1384 node
= (PlaylistNode
*) node
->next
;
1388 PlaylistRoot::Dump ()
1390 printf ("\n\nDUMP OF PLAYLIST\n\n");
1392 printf ("\n\nDUMP OF PLAYLIST DONE\n\n");
1397 PlaylistRoot::SeekCallback (EventObject
*obj
)
1399 PlaylistRoot
*playlist
= (PlaylistRoot
*) obj
;
1401 LOG_PLAYLIST ("Playlist::SeekCallback () pts: %" G_GUINT64_FORMAT
"\n", playlist
->seek_pts
);
1403 if (playlist
->seek_pts
!= G_MAXUINT64
) {
1404 guint64 pts
= playlist
->seek_pts
;
1405 playlist
->seek_pts
= G_MAXUINT64
;
1406 playlist
->Seek (pts
);
1411 PlaylistRoot::SeekAsync (guint64 pts
)
1413 LOG_PLAYLIST ("Playlist::SeekAsync (%" G_GUINT64_FORMAT
")\n", pts
);
1415 AddTickCall (SeekCallback
);
1419 PlaylistRoot::PlayCallback (EventObject
*obj
)
1421 LOG_PLAYLIST ("Playlist::PlayCallback ()\n");
1422 ((PlaylistRoot
*) obj
)->Play ();
1426 PlaylistRoot::PlayAsync ()
1428 LOG_PLAYLIST ("Playlist::PlayAsync ()\n");
1429 AddTickCall (PlayCallback
);
1433 PlaylistRoot::PauseCallback (EventObject
*obj
)
1435 LOG_PLAYLIST ("Playlist::PauseCallback ()\n");
1436 ((PlaylistRoot
*) obj
)->Pause ();
1440 PlaylistRoot::PauseAsync ()
1442 LOG_PLAYLIST ("Playlist::PauseAsync ()\n");
1443 AddTickCall (PauseCallback
);
1447 PlaylistRoot::OpenCallback (EventObject
*obj
)
1449 LOG_PLAYLIST ("Playlist::OpenCallback ()\n");
1450 ((PlaylistRoot
*) obj
)->Open ();
1454 PlaylistRoot::OpenAsync ()
1456 LOG_PLAYLIST ("Playlist::OpenAsync ()\n");
1457 AddTickCall (OpenCallback
);
1461 PlaylistRoot::StopCallback (EventObject
*obj
)
1463 LOG_PLAYLIST ("Playlist::StopCallback ()\n");
1464 ((PlaylistRoot
*) obj
)->Stop ();
1468 PlaylistRoot::StopAsync ()
1470 LOG_PLAYLIST ("Playlist::StopAsync ()\n");
1471 AddTickCall (StopCallback
);
1475 PlaylistRoot::Stop ()
1477 MediaPlayer
*mplayer
;
1479 LOG_PLAYLIST ("PlaylistRoot::Stop ()\n");
1481 mplayer
= GetMediaPlayer ();
1484 if (mplayer
!= NULL
)
1488 Emit (StopEvent
); // we emit the event after enqueuing the Open request, do avoid funky side-effects of event emission.
1492 PlaylistRoot::EmitBufferUnderflowEvent (EventObject
*obj
)
1494 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1495 root
->Emit (BufferUnderflowEvent
);
1499 PlaylistRoot::GetMediaPlayer ()
1505 PlaylistRoot::GetCurrentMedia ()
1507 PlaylistEntry
*entry
= GetCurrentEntry ();
1512 return entry
->GetMedia ();
1516 PlaylistRoot::GetElement ()
1522 PlaylistRoot::MediaEndedHandler (MediaPlayer
*mplayer
, EventArgs
*args
)
1524 LOG_PLAYLIST ("PlaylistRoot::MediaEndedHandler (%p, %p)\n", mplayer
, args
);
1528 // Emit (MediaEndedEvent, args);
1532 PlaylistRoot::BufferUnderflowHandler (MediaPlayer
*mplayer
, EventArgs
*args
)
1534 LOG_PLAYLIST ("PlaylistRoot::BufferUnderflowHandler (%p, %p)\n", mplayer
, args
);
1536 if (Surface::InMainThread ()) {
1537 EmitBufferUnderflowEvent (this);
1539 AddTickCall (EmitBufferUnderflowEvent
);
1547 PlaylistParser::PlaylistParser (PlaylistRoot
*root
, IMediaSource
*source
)
1550 this->source
= source
;
1551 this->internal
= NULL
;
1552 this->kind_stack
= NULL
;
1553 this->playlist
= NULL
;
1554 this->current_entry
= NULL
;
1555 this->current_text
= NULL
;
1556 this->error_args
= NULL
;
1560 PlaylistParser::SetSource (IMediaSource
*new_source
)
1564 source
= new_source
;
1570 PlaylistParser::Setup (XmlType type
)
1573 current_entry
= NULL
;
1574 current_text
= NULL
;
1576 was_playlist
= false;
1578 internal
= new PlaylistParserInternal ();
1579 kind_stack
= new List ();
1580 PushCurrentKind (PlaylistKind::Root
);
1582 if (type
== XML_TYPE_ASX3
) {
1583 XML_SetUserData (internal
->parser
, this);
1584 XML_SetElementHandler (internal
->parser
, on_asx_start_element
, on_asx_end_element
);
1585 XML_SetCharacterDataHandler (internal
->parser
, on_asx_text
);
1591 PlaylistParser::Cleanup ()
1594 kind_stack
->Clear (true);
1605 error_args
->unref ();
1610 PlaylistParser::~PlaylistParser ()
1616 str_match (const char *candidate
, const char *tag
)
1618 return g_ascii_strcasecmp (candidate
, tag
) == 0;
1622 PlaylistParser::on_asx_start_element (gpointer user_data
, const char *name
, const char **attrs
)
1624 ((PlaylistParser
*) user_data
)->OnASXStartElement (name
, attrs
);
1628 PlaylistParser::on_asx_end_element (gpointer user_data
, const char *name
)
1630 ((PlaylistParser
*) user_data
)->OnASXEndElement (name
);
1634 PlaylistParser::on_asx_text (gpointer user_data
, const char *data
, int len
)
1636 ((PlaylistParser
*) user_data
)->OnASXText (data
, len
);
1640 is_all_whitespace (const char *str
)
1645 for (int i
= 0; str
[i
] != 0; i
++) {
1660 // To make matters more interesting, the format of the VALUE attribute in the STARTTIME tag isn't
1661 // exactly the same as xaml's or javascript's TimeSpan format.
1663 // The time index, in hours, minutes, seconds, and hundredths of seconds.
1664 // [[hh]:mm]:ss.fract
1666 // The parser seems to stop if it finds a second dot, returnning whatever it had parsed
1669 // At most 4 digits of fract is read, the rest is ignored (even if it's not numbers).
1672 parse_int (const char **pp
, const char *end
, int *result
)
1674 const char *p
= *pp
;
1676 bool success
= false;
1678 while (p
<= end
&& g_ascii_isdigit (*p
)) {
1679 res
= res
* 10 + *p
- '0';
1692 duration_from_asx_str (PlaylistParser
*parser
, const char *str
, Duration
**res
)
1694 const char *end
= str
+ strlen (str
);
1697 int values
[] = {0, 0, 0};
1699 int hh
= 0, mm
= 0, ss
= 0;
1700 int milliseconds
= 0;
1705 if (!g_ascii_isdigit (*p
)) {
1706 parser
->ParsingError (new ErrorEventArgs (MediaError
, 2210, "AG_E_INVALID_ARGUMENT"));
1710 for (int i
= 0; i
< 3; i
++) {
1711 if (!parse_int (&p
, end
, &values
[i
])) {
1712 parser
->ParsingError (new ErrorEventArgs (MediaError
, 2210, "AG_E_INVALID_ARGUMENT"));
1723 while (digits
>= 0 && g_ascii_isdigit (*p
)) {
1724 milliseconds
+= pow (10.0f
, digits
) * (*p
- '0');
1728 if (counter
== 3 && *p
!= 0 && !g_ascii_isdigit (*p
)) {
1729 parser
->ParsingError (new ErrorEventArgs (MediaError
, 2210, "AG_E_INVALID_ARGUMENT"));
1748 parser
->ParsingError (new ErrorEventArgs (MediaError
, 2210, "AG_E_INVALID_ARGUMENT"));
1752 gint64 ms
= ((hh
* 3600) + (mm
* 60) + ss
) * 1000 + milliseconds
;
1753 TimeSpan result
= TimeSpan_FromPts (MilliSeconds_ToPts (ms
));
1754 Duration
*duration
= new Duration (result
);
1762 PlaylistParser::OnASXStartElement (const char *name
, const char **attrs
)
1764 PlaylistKind::Kind kind
= StringToKind (name
);
1768 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), kind = %d\n", name
, attrs
, kind
);
1770 g_free (current_text
);
1771 current_text
= NULL
;
1773 PushCurrentKind (kind
);
1776 case PlaylistKind::Abstract
:
1777 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1778 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1780 case PlaylistKind::Asx
:
1781 // Here the kind stack should be: Root+Asx
1782 if (kind_stack
->Length () != 2 || !AssertParentKind (PlaylistKind::Root
)) {
1783 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
1787 playlist
= new Playlist (root
, source
);
1789 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1790 if (str_match (attrs
[i
], "VERSION")) {
1791 if (str_match (attrs
[i
+1], "3")) {
1792 playlist_version
= 3;
1793 } else if (str_match (attrs
[i
+1], "3.0")) {
1794 playlist_version
= 3;
1796 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
1798 } else if (str_match (attrs
[i
], "BANNERBAR")) {
1799 ParsingError (new ErrorEventArgs (MediaError
, 3007, "Unsupported ASX attribute"));
1800 } else if (str_match (attrs
[i
], "PREVIEWMODE")) {
1801 ParsingError (new ErrorEventArgs (MediaError
, 3007, "Unsupported ASX attribute"));
1803 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1807 case PlaylistKind::Author
:
1808 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1809 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1811 case PlaylistKind::Banner
:
1812 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1813 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1815 case PlaylistKind::Base
:
1816 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
1818 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1819 if (str_match (attrs
[i
], "HREF")) {
1820 // TODO: What do we do with this value?
1821 if (GetCurrentContent () != NULL
) {
1824 if (!uri
->Parse (attrs
[i
+1], true)) {
1826 } else if (uri
->GetScheme() == NULL
) {
1828 } else if (uri
->IsScheme ("http") &&
1829 uri
->IsScheme ("https") &&
1830 uri
->IsScheme ("mms") &&
1831 uri
->IsScheme ("rtsp") &&
1832 uri
->IsScheme ("rstpt")) {
1837 GetCurrentContent ()->SetBase (uri
);
1840 ParsingError (new ErrorEventArgs (MediaError
, 4001, "AG_E_NETWORK_ERROR"));
1845 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1850 case PlaylistKind::Copyright
:
1851 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1852 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1854 case PlaylistKind::Duration
: {
1856 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1857 if (str_match (attrs
[i
], "VALUE")) {
1858 if (duration_from_asx_str (this, attrs
[i
+1], &dur
)) {
1859 if (GetCurrentEntry () != NULL
&& GetParentKind () != PlaylistKind::Ref
) {
1860 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name
, attrs
, TimeSpan_ToSecondsFloat (dur
->GetTimeSpan()));
1861 GetCurrentEntry ()->SetDuration (dur
);
1865 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1871 case PlaylistKind::Entry
: {
1872 bool client_skip
= true;
1873 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1874 if (str_match (attrs
[i
], "CLIENTSKIP")) {
1875 // TODO: What do we do with this value?
1876 if (str_match (attrs
[i
+1], "YES")) {
1878 } else if (str_match (attrs
[i
+1], "NO")) {
1879 client_skip
= false;
1881 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
1884 } else if (str_match (attrs
[i
], "SKIPIFREF")) {
1885 ParsingError (new ErrorEventArgs (MediaError
, 3007, "Unsupported ASX attribute"));
1888 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1892 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
1893 entry
->SetClientSkip (client_skip
);
1894 playlist
->AddEntry (entry
);
1895 current_entry
= entry
;
1898 case PlaylistKind::EntryRef
: {
1900 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1901 if (str_match (attrs
[i
], "HREF")) {
1903 href
= g_strdup (attrs
[i
+1]);
1904 // Docs says this attribute isn't unsupported, but an error is emitted.
1905 //} else if (str_match (attrs [i], "CLIENTBIND")) {
1906 // // TODO: What do we do with this value?
1908 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1915 if (!uri
->Parse (href
)) {
1918 ParsingError (new ErrorEventArgs (MediaError
, 1001, "AG_E_UNKNOWN_ERROR"));
1922 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
1924 entry
->SetSourceName (uri
);
1926 playlist
->AddEntry (entry
);
1927 current_entry
= entry
;
1930 case PlaylistKind::LogUrl
:
1931 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1932 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1934 case PlaylistKind::MoreInfo
:
1935 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1936 if (str_match (attrs
[i
], "HREF")) {
1937 if (GetCurrentEntry () != NULL
)
1938 GetCurrentEntry ()->SetInfoURL (attrs
[i
+1]);
1939 } else if (str_match (attrs
[i
], "TARGET")) {
1940 if (GetCurrentEntry () != NULL
)
1941 GetCurrentEntry ()->SetInfoTarget (attrs
[i
+1]);
1943 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1948 case PlaylistKind::StartTime
: {
1950 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1951 if (str_match (attrs
[i
], "VALUE")) {
1952 if (duration_from_asx_str (this, attrs
[i
+1], &dur
) && dur
->HasTimeSpan ()) {
1953 if (GetCurrentEntry () != NULL
&& GetParentKind () != PlaylistKind::Ref
) {
1954 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name
, attrs
, TimeSpan_ToSecondsFloat (dur
->GetTimeSpan()));
1955 GetCurrentEntry ()->SetStartTime (dur
->GetTimeSpan ());
1959 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1966 case PlaylistKind::Ref
: {
1967 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1968 if (str_match (attrs
[i
], "HREF")) {
1969 if (GetCurrentEntry () != NULL
&& GetCurrentEntry ()->GetSourceName () == NULL
) {
1971 if (uri
->Parse (attrs
[i
+1])) {
1972 GetCurrentEntry ()->SetSourceName (uri
);
1975 ParsingError (new ErrorEventArgs (MediaError
, 1001, "AG_E_UNKNOWN_ERROR"));
1980 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
1986 case PlaylistKind::Param
: {
1987 const char *name
= NULL
;
1988 const char *value
= NULL
;
1990 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1991 if (str_match (attrs
[i
], "name")) {
1992 name
= attrs
[i
+ 1];
1993 } else if (str_match (attrs
[i
], "value")) {
1994 value
= attrs
[i
+ 1];
1996 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
2000 if (value
!= NULL
&& value
[0] != 0 && name
!= NULL
&& name
[0] != 0) {
2001 PlaylistEntry
*entry
= GetCurrentEntry ();
2006 entry
->AddParams (name
, value
);
2012 case PlaylistKind::Title
:
2013 if (attrs
!= NULL
&& attrs
[0] != NULL
)
2014 ParsingError (new ErrorEventArgs (MediaError
, 3005, "Invalid ASX attribute"));
2016 case PlaylistKind::StartMarker
:
2017 case PlaylistKind::EndMarker
:
2018 case PlaylistKind::Repeat
:
2019 case PlaylistKind::Event
:
2020 ParsingError (new ErrorEventArgs (MediaError
, 3006, "Unsupported ASX element"));
2022 case PlaylistKind::Root
:
2023 case PlaylistKind::Unknown
:
2025 LOG_PLAYLIST ("PlaylistParser::OnStartElement ('%s', %p): Unknown kind: %d\n", name
, attrs
, kind
);
2026 ParsingError (new ErrorEventArgs (MediaError
, 3004, "Invalid ASX element"));
2032 PlaylistParser::OnASXEndElement (const char *name
)
2034 PlaylistKind::Kind kind
= GetCurrentKind ();
2037 LOG_PLAYLIST ("PlaylistParser::OnEndElement (%s), GetCurrentKind (): %d, GetCurrentKind () to string: %s\n", name
, kind
, KindToString (kind
));
2040 case PlaylistKind::Abstract
:
2041 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2043 if (GetCurrentContent () != NULL
)
2044 GetCurrentContent ()->SetAbstract (current_text
);
2046 case PlaylistKind::Author
:
2047 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2049 if (GetCurrentContent () != NULL
)
2050 GetCurrentContent ()->SetAuthor (current_text
);
2052 case PlaylistKind::Base
:
2053 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2056 case PlaylistKind::Copyright
:
2057 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2059 if (GetCurrentContent () != NULL
)
2060 GetCurrentContent ()->SetCopyright (current_text
);
2062 case PlaylistKind::Duration
:
2063 if (!AssertParentKind (PlaylistKind::Entry
| PlaylistKind::Ref
))
2065 if (current_text
== NULL
)
2067 duration_from_asx_str (this, current_text
, &dur
);
2068 if (GetCurrentEntry () != NULL
)
2069 GetCurrentEntry ()->SetDuration (dur
);
2071 case PlaylistKind::Entry
:
2072 if (!AssertParentKind (PlaylistKind::Asx
))
2074 if (!is_all_whitespace (current_text
)) {
2075 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
2078 case PlaylistKind::EntryRef
:
2079 if (!AssertParentKind (PlaylistKind::Asx
))
2082 case PlaylistKind::StartTime
:
2083 if (!AssertParentKind (PlaylistKind::Entry
| PlaylistKind::Ref
))
2085 if (!is_all_whitespace (current_text
)) {
2086 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
2089 case PlaylistKind::Title
:
2090 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2092 if (GetCurrentContent () != NULL
)
2093 GetCurrentContent ()->SetTitle (current_text
);
2095 case PlaylistKind::Asx
:
2096 if (playlist_version
== 3)
2097 was_playlist
= true;
2098 if (!AssertParentKind (PlaylistKind::Root
))
2101 case PlaylistKind::Ref
:
2102 if (!AssertParentKind (PlaylistKind::Entry
))
2104 if (!is_all_whitespace (current_text
)) {
2105 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
2108 case PlaylistKind::MoreInfo
:
2109 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2111 if (!is_all_whitespace (current_text
)) {
2112 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
2115 case PlaylistKind::Param
:
2116 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2118 if (!is_all_whitespace (current_text
)) {
2119 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
2123 LOG_PLAYLIST ("PlaylistParser::OnEndElement ('%s'): Unknown kind %d.\n", name
, kind
);
2124 ParsingError (new ErrorEventArgs (MediaError
, 3004, "Invalid ASX element"));
2128 if (current_text
!= NULL
) {
2129 g_free (current_text
);
2130 current_text
= NULL
;
2133 switch (GetCurrentKind ()) {
2134 case PlaylistKind::Entry
:
2144 PlaylistParser::OnASXText (const char *text
, int len
)
2146 char *a
= g_strndup (text
, len
);
2149 char *p
= g_strndup (text
, len
);
2150 for (int i
= 0; p
[i
] != 0; i
++)
2151 if (p
[i
] == 10 || p
[i
] == 13)
2154 LOG_PLAYLIST ("PlaylistParser::OnText (%s, %d)\n", p
, len
);
2158 if (current_text
== NULL
) {
2161 char *b
= g_strconcat (current_text
, a
, NULL
);
2162 g_free (current_text
);
2168 PlaylistParser::Is (IMediaSource
*source
, const char *asx_header
)
2170 bool result
= false;
2171 int asx_header_length
= strlen (asx_header
);
2172 unsigned char buffer
[20];
2175 result
= source
->Peek ((guint8
*) buffer
, asx_header_length
);
2179 // skip any whitespace
2180 unsigned char c
= buffer
[0];
2186 result
= source
->ReadAll ((guint8
*) buffer
, 1);
2192 if (buffer
[1] == 0xbb && buffer
[2] == 0xbf) { // UTF-8 BOM: EF BB BF
2193 result
= source
->ReadAll ((guint8
*) buffer
, 3);
2198 // TODO: there might be other BOMs we should handle too
2202 result
= !g_ascii_strncasecmp ((const char *) buffer
, asx_header
, asx_header_length
);
2209 source
->Seek (0, SEEK_SET
);
2215 PlaylistParser::IsASX3 (IMediaSource
*source
)
2217 return Is (source
, "<ASX");
2221 PlaylistParser::IsASX2 (IMediaSource
*source
)
2223 return Is (source
, "[Reference]");
2227 PlaylistParser::ParseASX2 ()
2229 const int BUFFER_SIZE
= 1024;
2231 char buffer
[BUFFER_SIZE
];
2237 playlist_version
= 2;
2239 bytes_read
= source
->ReadSome (buffer
, BUFFER_SIZE
);
2240 if (bytes_read
< 0) {
2241 LOG_PLAYLIST_WARN ("Could not read asx document for parsing.\n");
2245 key_file
= g_key_file_new ();
2246 if (!g_key_file_load_from_data (key_file
, buffer
, bytes_read
,
2247 G_KEY_FILE_NONE
, NULL
)) {
2248 LOG_PLAYLIST_WARN ("Invalid asx2 document.\n");
2249 g_key_file_free (key_file
);
2253 ref
= g_key_file_get_value (key_file
, "Reference", "Ref1", NULL
);
2255 LOG_PLAYLIST_WARN ("Could not find Ref1 entry in asx2 document.\n");
2256 g_key_file_free (key_file
);
2260 if (!g_str_has_prefix (ref
, "http://") || !g_str_has_suffix (ref
, "MSWMExt=.asf")) {
2261 LOG_PLAYLIST_WARN ("Could not find a valid uri within Ref1 entry in asx2 document.\n");
2263 g_key_file_free (key_file
);
2267 mms_uri
= g_strdup_printf ("mms://%s", strstr (ref
, "http://") + strlen ("http://"));
2269 g_key_file_free (key_file
);
2272 playlist
= new Playlist (root
, source
);
2274 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
2276 if (uri
->Parse (mms_uri
)) {
2277 entry
->SetSourceName (uri
);
2281 playlist
->AddEntry (entry
);
2282 current_entry
= entry
;
2288 PlaylistParser::TryFixError (gint8
*current_buffer
, int bytes_read
)
2292 if (XML_GetErrorCode (internal
->parser
) != XML_ERROR_INVALID_TOKEN
)
2295 int index
= XML_GetErrorByteIndex (internal
->parser
);
2297 if (index
> bytes_read
)
2300 LOG_PLAYLIST ("Attempting to fix invalid token error index: %d\n", index
);
2302 // OK, so we are going to guess that we are in an attribute here and walk back
2303 // until we hit a control char that should be escaped.
2304 char * escape
= NULL
;
2305 while (index
>= 0) {
2306 switch (current_buffer
[index
]) {
2308 escape
= g_strdup ("&");
2311 escape
= g_strdup ("<");
2314 escape
= g_strdup (">");
2325 LOG_PLAYLIST_WARN ("Unable to find an invalid escape character to fix in ASX: %s.\n", current_buffer
);
2330 int escape_len
= strlen (escape
);
2331 int new_size
= source
->GetSize () + escape_len
- 1;
2332 int patched_size
= internal
->bytes_read
+ bytes_read
+ escape_len
- 1;
2333 gint8
* new_buffer
= (gint8
*) g_malloc (new_size
);
2335 source
->Seek (0, SEEK_SET
);
2336 source
->ReadSome (new_buffer
, internal
->bytes_read
);
2338 memcpy (new_buffer
+ internal
->bytes_read
, current_buffer
, index
);
2339 memcpy (new_buffer
+ internal
->bytes_read
+ index
, escape
, escape_len
);
2340 memcpy (new_buffer
+ internal
->bytes_read
+ index
+ escape_len
, current_buffer
+ index
+ 1, bytes_read
- index
- 1);
2342 source
->Seek (internal
->bytes_read
+ bytes_read
, SEEK_SET
);
2343 source
->ReadSome (new_buffer
+ patched_size
, new_size
- patched_size
);
2345 media
= source
->GetMediaReffed ();
2347 MemorySource
*reparse_source
= new MemorySource (media
, new_buffer
, new_size
);
2348 SetSource (reparse_source
);
2349 reparse_source
->unref ();
2351 internal
->reparse
= true;
2362 PlaylistParser::Parse ()
2365 gint64 last_available_pos
;
2368 LOG_PLAYLIST ("PlaylistParser::Parse ()\n");
2371 // Don't try to parse anything until we have all the data.
2372 if (internal
!= NULL
)
2373 internal
->reparse
= false;
2374 size
= source
->GetSize ();
2375 last_available_pos
= source
->GetLastAvailablePosition ();
2376 if (size
!= -1 && last_available_pos
!= -1 && size
!= last_available_pos
)
2377 return MEDIA_NOT_ENOUGH_DATA
;
2379 if (this->IsASX2 (source
)) {
2380 /* Parse as a asx2 mms file */
2381 Setup (XML_TYPE_NONE
);
2382 result
= this->ParseASX2 ();
2383 } else if (this->IsASX3 (source
)) {
2384 Setup (XML_TYPE_ASX3
);
2385 result
= this->ParseASX3 ();
2389 } while (result
&& internal
->reparse
);
2391 return result
? MEDIA_SUCCESS
: MEDIA_FAIL
;
2395 PlaylistParser::ParseASX3 ()
2400 // asx documents don't tend to be very big, so there's no need for a big buffer
2401 const int BUFFER_SIZE
= 1024;
2404 buffer
= XML_GetBuffer(internal
->parser
, BUFFER_SIZE
);
2405 if (buffer
== NULL
) {
2406 fprintf (stderr
, "Could not allocate memory for asx document parsing.\n");
2410 bytes_read
= source
->ReadSome (buffer
, BUFFER_SIZE
);
2411 if (bytes_read
< 0) {
2412 fprintf (stderr
, "Could not read asx document for parsing.\n");
2416 if (!XML_ParseBuffer (internal
->parser
, bytes_read
, bytes_read
== 0)) {
2417 if (error_args
!= NULL
)
2420 switch (XML_GetErrorCode (internal
->parser
)) {
2421 case XML_ERROR_NO_ELEMENTS
:
2422 ParsingError (new ErrorEventArgs (MediaError
, 7000, "unexpected end of input"));
2424 case XML_ERROR_DUPLICATE_ATTRIBUTE
:
2425 ParsingError (new ErrorEventArgs (MediaError
, 7031, "wfc: unique attribute spec"));
2427 case XML_ERROR_INVALID_TOKEN
:
2428 // save error args in case the error fixing fails (in which case we want this error, not the error the error fixing caused)
2429 error_args
= new ErrorEventArgs (MediaError
, 7007, "quote expected");
2430 if (TryFixError ((gint8
*) buffer
, bytes_read
))
2434 char *msg
= g_strdup_printf ("%s %d (%d, %d)",
2435 XML_ErrorString (XML_GetErrorCode (internal
->parser
)), (int) XML_GetErrorCode (internal
->parser
),
2436 (int) XML_GetCurrentLineNumber (internal
->parser
), (int) XML_GetCurrentColumnNumber (internal
->parser
));
2437 ParsingError (new ErrorEventArgs (MediaError
, 3000, msg
));
2443 if (bytes_read
== 0)
2446 internal
->bytes_read
+= bytes_read
;
2449 return playlist
!= NULL
;
2453 PlaylistParser::GetCurrentContent ()
2455 if (current_entry
!= NULL
)
2456 return current_entry
;
2462 PlaylistParser::GetCurrentEntry ()
2464 return current_entry
;
2468 PlaylistParser::EndEntry ()
2470 this->current_entry
= NULL
;
2474 PlaylistParser::PushCurrentKind (PlaylistKind::Kind kind
)
2476 kind_stack
->Append (new KindNode (kind
));
2477 LOG_PLAYLIST ("PlaylistParser::Push (%d)\n", kind
);
2481 PlaylistParser::PopCurrentKind ()
2483 LOG_PLAYLIST ("PlaylistParser::PopCurrentKind (), current: %d\n", ((KindNode
*)kind_stack
->Last ())->kind
);
2484 kind_stack
->Remove (kind_stack
->Last ());
2488 PlaylistParser::GetCurrentKind ()
2490 KindNode
*node
= (KindNode
*) kind_stack
->Last ();
2495 PlaylistParser::GetParentKind ()
2497 KindNode
*node
= (KindNode
*) kind_stack
->Last ()->prev
;
2502 PlaylistParser::AssertParentKind (int kind
)
2504 LOG_PLAYLIST ("PlaylistParser::AssertParentKind (%d), GetParentKind: %d, result: %d\n", kind
, GetParentKind (), GetParentKind () & kind
);
2506 if (GetParentKind () & kind
)
2509 ParsingError (new ErrorEventArgs (MediaError
, 3008, "ASX parse error"));
2515 PlaylistParser::ParsingError (ErrorEventArgs
*args
)
2517 LOG_PLAYLIST ("PlaylistParser::ParsingError (%s)\n", args
->error_message
);
2519 XML_StopParser (internal
->parser
, false);
2523 return; // don't overwrite any previous errors.
2525 error_args
= args
; // don't ref, this method is called like this: ParsingError (new ErrorEventArgs (...));, so the caller gives us the ref he has
2529 PlaylistKind
PlaylistParser::playlist_kinds
[] = {
2531 PlaylistKind ("ABSTRACT", PlaylistKind::Abstract
),
2532 PlaylistKind ("ASX", PlaylistKind::Asx
),
2533 PlaylistKind ("ROOT", PlaylistKind::Root
),
2534 PlaylistKind ("AUTHOR", PlaylistKind::Author
),
2535 PlaylistKind ("BANNER", PlaylistKind::Banner
),
2536 PlaylistKind ("BASE", PlaylistKind::Base
),
2537 PlaylistKind ("COPYRIGHT", PlaylistKind::Copyright
),
2538 PlaylistKind ("DURATION", PlaylistKind::Duration
),
2539 PlaylistKind ("ENTRY", PlaylistKind::Entry
),
2540 PlaylistKind ("ENTRYREF", PlaylistKind::EntryRef
),
2541 PlaylistKind ("LOGURL", PlaylistKind::LogUrl
),
2542 PlaylistKind ("MOREINFO", PlaylistKind::MoreInfo
),
2543 PlaylistKind ("REF", PlaylistKind::Ref
),
2544 PlaylistKind ("STARTTIME", PlaylistKind::StartTime
),
2545 PlaylistKind ("TITLE", PlaylistKind::Title
),
2546 PlaylistKind ("STARTMARKER", PlaylistKind::StartMarker
),
2547 PlaylistKind ("REPEAT", PlaylistKind::Repeat
),
2548 PlaylistKind ("ENDMARKER", PlaylistKind::EndMarker
),
2549 PlaylistKind ("PARAM", PlaylistKind::Param
),
2550 PlaylistKind ("EVENT", PlaylistKind::Event
),
2552 PlaylistKind (NULL
, PlaylistKind::Unknown
)
2556 PlaylistParser::StringToKind (const char *str
)
2558 PlaylistKind::Kind kind
= PlaylistKind::Unknown
;
2560 for (int i
= 0; playlist_kinds
[i
].str
!= NULL
; i
++) {
2561 if (str_match (str
, playlist_kinds
[i
].str
)) {
2562 kind
= playlist_kinds
[i
].kind
;
2567 LOG_PLAYLIST ("PlaylistParser::StringToKind ('%s') = %d\n", str
, kind
);
2573 PlaylistParser::KindToString (PlaylistKind::Kind kind
)
2575 const char *result
= NULL
;
2577 for (int i
= 0; playlist_kinds
[i
].str
!= NULL
; i
++) {
2578 if (playlist_kinds
[i
].kind
== kind
) {
2579 result
= playlist_kinds
[i
].str
;
2584 LOG_PLAYLIST ("PlaylistParser::KindToString (%d) = '%s'\n", kind
, result
);