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
, false)
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
)
76 : EventObject (kind
, false)
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
)
86 : EventObject (kind
, false)
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
->GetErrorMessage() : "?");
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
);
869 root
->Emit (PlaylistRoot::PlayEvent
);
873 PlaylistEntry::Pause ()
875 MediaPlayer
*mplayer
= GetMediaPlayer ();
876 PlaylistRoot
*root
= GetRoot ();
878 LOG_PLAYLIST ("PlaylistEntry::Pause ()\n");
880 g_return_if_fail (media
!= NULL
);
881 g_return_if_fail (mplayer
!= NULL
);
882 g_return_if_fail (root
!= NULL
);
884 play_when_available
= false;
885 media
->PauseAsync ();
888 root
->Emit (PlaylistRoot::PauseEvent
);
892 PlaylistEntry::Stop ()
894 LOG_PLAYLIST ("PlaylistEntry::Stop ()\n");
896 play_when_available
= false;
902 PlaylistEntry::GetMedia ()
908 PlaylistEntry::IsSingleFile ()
910 return parent
? parent
->IsSingleFile () : false;
914 PlaylistEntry::GetRoot ()
921 if (parent
== NULL
) {
922 g_return_val_if_fail (GetObjectType () == Type::PLAYLISTROOT
, NULL
);
923 return (PlaylistRoot
*) this;
928 while (pl
->parent
!= NULL
)
931 g_return_val_if_fail (pl
->GetObjectType () == Type::PLAYLISTROOT
, NULL
);
933 return (PlaylistRoot
*) pl
;
940 Playlist::Playlist (Playlist
*parent
, IMediaSource
*source
)
941 : PlaylistEntry (Type::PLAYLIST
, parent
)
943 is_single_file
= false;
947 this->source
= source
;
948 this->source
->ref ();
951 Playlist::Playlist (Type::Kind kind
)
952 : PlaylistEntry (kind
)
954 LOG_PLAYLIST ("Playlist::Playlist ()\n");
955 is_single_file
= true;
958 AddEntry (new PlaylistEntry (this));
964 LOG_PLAYLIST ("Playlist::Init ()\n");
966 entries
= new List ();
975 PlaylistEntry
*entry
;
977 LOG_PLAYLIST ("Playlist::Dispose () id: %i\n", GET_OBJ_ID (this));
981 if (entries
!= NULL
) {
982 node
= (PlaylistNode
*) entries
->First ();
983 while (node
!= NULL
) {
984 entry
= node
->GetEntry ();
987 node
= (PlaylistNode
*) node
->next
;
998 PlaylistEntry::Dispose ();
1003 Playlist::IsCurrentEntryLastEntry ()
1005 PlaylistEntry
*entry
;
1008 if (entries
->Last () == NULL
)
1011 if (current_node
!= entries
->Last ())
1014 entry
= GetCurrentEntry ();
1016 if (!entry
->IsPlaylist ())
1019 pl
= (Playlist
*) entry
;
1021 return pl
->IsCurrentEntryLastEntry ();
1027 PlaylistEntry
*current_entry
;
1029 LOG_PLAYLIST ("Playlist::Open ()\n");
1031 current_node
= (PlaylistNode
*) entries
->First ();
1033 current_entry
= GetCurrentEntry ();
1035 while (current_entry
&& current_entry
->HasDuration () && current_entry
->GetDuration ()->HasTimeSpan() &&
1036 current_entry
->GetDuration ()->GetTimeSpan () == 0) {
1037 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry
->GetSourceName ()->ToString ());
1038 current_node
= (PlaylistNode
*) current_node
->next
;
1039 current_entry
= GetCurrentEntry ();
1043 current_entry
->Open ();
1047 LOG_PLAYLIST ("Playlist::Open (): current node: %p, current entry: %p\n", current_entry
, GetCurrentEntry ());
1051 Playlist::PlayNext ()
1053 PlaylistEntry
*current_entry
;
1054 MediaElement
*element
= GetElement ();
1055 PlaylistRoot
*root
= GetRoot ();
1057 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p\n", current_node
);
1058 g_return_val_if_fail (root
!= NULL
, false);
1065 current_entry
= GetCurrentEntry ();
1067 if (current_entry
->HasDuration() && current_entry
->GetDuration()->IsForever ()) {
1068 element
->SetPlayRequested ();
1069 current_entry
->Play ();
1073 if (current_entry
->IsPlaylist ()) {
1074 Playlist
*current_playlist
= (Playlist
*) current_entry
;
1075 if (current_playlist
->PlayNext ())
1079 if (current_node
->next
) {
1080 current_node
= (PlaylistNode
*) current_node
->next
;
1082 current_entry
= GetCurrentEntry ();
1083 if (current_entry
) {
1084 LOG_PLAYLIST ("Playlist::PlayNext () playing entry: %p %s\n", current_entry
, current_entry
->GetFullSourceName ());
1085 element
->SetPlayRequested ();
1086 root
->Emit (PlaylistRoot::EntryChangedEvent
);
1087 current_entry
->Open ();
1092 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p, nothing to play (is root: %i)\n", current_node
, GetObjectType () == Type::PLAYLISTROOT
);
1094 if (GetObjectType () == Type::PLAYLISTROOT
)
1095 root
->Emit (PlaylistRoot::MediaEndedEvent
);
1101 Playlist::OnEntryEnded ()
1103 LOG_PLAYLIST ("Playlist::OnEntryEnded ()\n");
1108 Playlist::OnEntryFailed (ErrorEventArgs
*args
)
1111 PlaylistRoot
*root
= GetRoot ();
1113 LOG_PLAYLIST ("Playlist::OnEntryFailed () extended_code: %i is_single_file: %i\n", args
? args
->GetExtendedCode() : 0, is_single_file
);
1115 g_return_if_fail (root
!= NULL
);
1117 // media or playlist 404: fatal
1118 // invalid playlist (playlist parsing failed): fatal
1119 // invalid media (gif, swf): play next
1123 // check if we're in a playlist
1124 if (GetMedia () != NULL
&& GetMedia ()->GetDemuxer () != NULL
&& GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER
) {
1126 if (args
->GetExtendedCode() == MEDIA_UNKNOWN_CODEC
) {
1132 // we're not a playlist
1140 root
->Emit (PlaylistRoot::MediaErrorEvent
, args
);
1147 Playlist::Seek (guint64 pts
)
1149 PlaylistEntry
*current_entry
;
1151 LOG_PLAYLIST ("Playlist::Seek (%llu)\n", pts
);
1153 current_entry
= GetCurrentEntry ();
1155 g_return_if_fail (current_entry
!= NULL
);
1157 current_entry
->Seek (pts
);
1163 PlaylistEntry
*current_entry
;
1165 LOG_PLAYLIST ("Playlist::Play ()\n");
1167 current_entry
= GetCurrentEntry ();
1169 g_return_if_fail (current_entry
!= NULL
);
1171 if (current_entry
&& current_entry
->HasDuration () && current_entry
->GetDuration () == 0) {
1172 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry
->GetSourceName ()->ToString ());
1176 current_entry
->Play ();
1183 PlaylistEntry
*current_entry
;
1185 LOG_PLAYLIST ("Playlist::Pause ()\n");
1187 current_entry
= GetCurrentEntry ();
1189 g_return_if_fail (current_entry
!= NULL
);
1191 current_entry
->Pause ();
1199 LOG_PLAYLIST ("Playlist::Stop ()\n");
1201 node
= (PlaylistNode
*) entries
->First ();
1202 current_node
= node
; // reset to first node
1203 while (node
!= NULL
) {
1204 node
->GetEntry ()->Stop ();
1205 node
= (PlaylistNode
*) node
->next
;
1210 Playlist::PopulateMediaAttributes ()
1212 PlaylistEntry
*current_entry
= GetCurrentEntry ();
1214 LOG_PLAYLIST ("Playlist::PopulateMediaAttributes ()\n");
1219 current_entry
->PopulateMediaAttributes ();
1223 Playlist::AddEntry (PlaylistEntry
*entry
)
1227 LOG_PLAYLIST ("Playlist::AddEntry (%p) Count: %i\n", entry
, entries
->Length ());
1229 node
= new PlaylistNode (entry
);
1230 entries
->Append (node
);
1233 if (entries
->Length () == 1) {
1234 g_return_if_fail (current_node
== NULL
);
1235 current_node
= node
;
1240 Playlist::ReplaceCurrentEntry (Playlist
*pl
)
1244 PlaylistEntry
*current_entry
= GetCurrentEntry ();
1246 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntry (%p)\n", pl
);
1248 // check for too nested playlist
1250 PlaylistEntry
*e
= this;
1251 while (e
!= NULL
&& e
->IsPlaylist ()) {
1252 if (e
->GetObjectType () != Type::PLAYLISTROOT
&& e
->GetMedia () != NULL
&& e
->GetMedia ()->GetDemuxer () != NULL
&& e
->GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER
)
1254 e
= e
->GetParent ();
1257 ErrorEventArgs
*args
= new ErrorEventArgs (MediaError
,
1258 MoonError (MoonError::EXCEPTION
, 4001, "AG_E_NETWORK_ERROR"));
1259 OnEntryFailed (args
);
1265 if (current_entry
->IsPlaylist ()) {
1266 result
= ((Playlist
*) current_entry
)->ReplaceCurrentEntry (pl
);
1268 PlaylistNode
*pln
= new PlaylistNode (pl
);
1269 pl
->MergeWith (current_entry
);
1270 entries
->InsertBefore (pln
, current_node
);
1271 entries
->Remove (current_node
);
1272 pl
->SetParent (this);
1277 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntrY (%p) [DONE]\n", pl
);
1283 Playlist::MergeWith (PlaylistEntry
*entry
)
1285 LOG_PLAYLIST ("Playlist::MergeWith (%p)\n", entry
);
1287 SetBase (entry
->GetBase () ? new Uri (*entry
->GetBase ()) : NULL
);
1288 SetTitle (entry
->GetTitle ());
1289 SetAuthor (entry
->GetAuthor ());
1290 SetAbstract (entry
->GetAbstract ());
1291 SetCopyright (entry
->GetCopyright ());
1293 SetSourceName (entry
->GetSourceName () ? new Uri (*entry
->GetSourceName ()) : NULL
);
1294 if (entry
->HasDuration ())
1295 SetDuration (entry
->GetDuration ());
1296 Initialize (entry
->GetMedia ());
1297 entry
->ClearMedia ();
1301 Playlist::GetCurrentPlaylistEntry ()
1303 PlaylistEntry
*result
= NULL
;
1306 result
= current_node
->GetEntry () ->GetCurrentPlaylistEntry ();
1314 PlaylistRoot::PlaylistRoot (MediaElement
*element
)
1315 : Playlist (Type::PLAYLISTROOT
)
1317 this->element
= element
;
1320 mplayer
= element
->GetMediaPlayer ();
1321 mplayer
->AddHandler (MediaPlayer::MediaEndedEvent
, MediaEndedCallback
, this);
1322 mplayer
->AddHandler (MediaPlayer::BufferUnderflowEvent
, BufferUnderflowCallback
, this);
1327 PlaylistRoot::Dispose ()
1329 if (mplayer
!= NULL
) {
1330 mplayer
->RemoveAllHandlers (this);
1335 Playlist::Dispose ();
1339 PlaylistRoot::IsSingleFile ()
1341 PlaylistEntry
*entry
;
1343 if (GetCount () != 1)
1346 entry
= GetCurrentEntry ();
1350 if (entry
->GetObjectType () == Type::PLAYLISTENTRY
)
1353 return entry
->IsSingleFile ();
1358 PlaylistEntry::DumpInternal (int tabs
)
1360 printf ("%*s%s %i\n", tabs
, "", GetTypeName (), GET_OBJ_ID (this));
1362 printf ("%*sParent: %p %s\n", tabs
, "", parent
, parent
? parent
->GetTypeName () : NULL
);
1363 printf ("%*sFullSourceName: %s\n", tabs
, "", GetFullSourceName ());
1364 printf ("%*sDuration: %s %.2f seconds\n", tabs
, "", HasDuration () ? "yes" : "no", HasDuration () ? GetDuration ()->ToSecondsFloat () : 0.0);
1365 printf ("%*sMedia: %i %s\n", tabs
, "", GET_OBJ_ID (media
), media
? "" : "(null)");
1367 printf ("%*sUri: %s\n", tabs
, "", media
->GetUri ());
1368 printf ("%*sDemuxer: %i %s\n", tabs
, "", GET_OBJ_ID (media
->GetDemuxer ()), media
->GetDemuxer () ? media
->GetDemuxer ()->GetTypeName () : "N/A");
1369 printf ("%*sSource: %i %s\n", tabs
, "", GET_OBJ_ID (media
->GetSource ()), media
->GetSource () ? media
->GetSource ()->GetTypeName () : "N/A");
1375 Playlist::DumpInternal (int tabs
)
1379 PlaylistEntry::DumpInternal (tabs
);
1380 printf ("%*s %i entries:\n", tabs
, "", entries
->Length ());
1381 node
= (PlaylistNode
*) entries
->First ();
1382 while (node
!= NULL
) {
1383 if (node
== current_node
)
1384 printf ("*%*s * CURRENT NODE *\n", tabs
, "");
1385 node
->GetEntry ()->DumpInternal (tabs
+ 2);
1386 node
= (PlaylistNode
*) node
->next
;
1390 PlaylistRoot::Dump ()
1392 printf ("\n\nDUMP OF PLAYLIST\n\n");
1394 printf ("\n\nDUMP OF PLAYLIST DONE\n\n");
1399 PlaylistRoot::SeekCallback (EventObject
*obj
)
1401 PlaylistRoot
*playlist
= (PlaylistRoot
*) obj
;
1403 LOG_PLAYLIST ("Playlist::SeekCallback () pts: %" G_GUINT64_FORMAT
"\n", playlist
->seek_pts
);
1405 if (playlist
->IsDisposed ())
1408 if (playlist
->seek_pts
!= G_MAXUINT64
) {
1409 guint64 pts
= playlist
->seek_pts
;
1410 playlist
->seek_pts
= G_MAXUINT64
;
1411 playlist
->Seek (pts
);
1416 PlaylistRoot::SeekAsync (guint64 pts
)
1418 LOG_PLAYLIST ("Playlist::SeekAsync (%" G_GUINT64_FORMAT
")\n", pts
);
1420 AddTickCall (SeekCallback
);
1424 PlaylistRoot::PlayCallback (EventObject
*obj
)
1426 LOG_PLAYLIST ("Playlist::PlayCallback ()\n");
1428 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1429 if (root
->IsDisposed ())
1435 PlaylistRoot::PlayAsync ()
1437 LOG_PLAYLIST ("Playlist::PlayAsync ()\n");
1438 AddTickCall (PlayCallback
);
1442 PlaylistRoot::PauseCallback (EventObject
*obj
)
1444 LOG_PLAYLIST ("Playlist::PauseCallback ()\n");
1446 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1447 if (root
->IsDisposed ())
1453 PlaylistRoot::PauseAsync ()
1455 LOG_PLAYLIST ("Playlist::PauseAsync ()\n");
1456 AddTickCall (PauseCallback
);
1460 PlaylistRoot::OpenCallback (EventObject
*obj
)
1462 LOG_PLAYLIST ("Playlist::OpenCallback ()\n");
1464 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1465 if (root
->IsDisposed ())
1471 PlaylistRoot::OpenAsync ()
1473 LOG_PLAYLIST ("Playlist::OpenAsync ()\n");
1474 AddTickCall (OpenCallback
);
1478 PlaylistRoot::StopCallback (EventObject
*obj
)
1480 LOG_PLAYLIST ("Playlist::StopCallback ()\n");
1482 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1483 if (root
->IsDisposed ())
1489 PlaylistRoot::StopAsync ()
1491 LOG_PLAYLIST ("Playlist::StopAsync ()\n");
1492 AddTickCall (StopCallback
);
1496 PlaylistRoot::Stop ()
1498 MediaPlayer
*mplayer
;
1500 LOG_PLAYLIST ("PlaylistRoot::Stop ()\n");
1502 mplayer
= GetMediaPlayer ();
1505 if (mplayer
!= NULL
)
1507 // Stop is called async, and if we now emit Open async, we'd possibly not get events in the right order
1508 // example with user code:
1511 // would end up like:
1512 // StopAsync (); -> enqueue Stop
1513 // PlayAsync (); -> enqueue Play
1514 // Stop is called, enqueue Open
1516 Emit (StopEvent
); // we emit the event after enqueuing the Open request, do avoid funky side-effects of event emission.
1520 PlaylistRoot::EmitBufferUnderflowEvent (EventObject
*obj
)
1522 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1523 root
->Emit (BufferUnderflowEvent
);
1527 PlaylistRoot::GetMediaPlayer ()
1533 PlaylistRoot::GetCurrentMedia ()
1535 PlaylistEntry
*entry
= GetCurrentEntry ();
1540 return entry
->GetMedia ();
1544 PlaylistRoot::GetElement ()
1550 PlaylistRoot::MediaEndedHandler (MediaPlayer
*mplayer
, EventArgs
*args
)
1552 LOG_PLAYLIST ("PlaylistRoot::MediaEndedHandler (%p, %p)\n", mplayer
, args
);
1556 // Emit (MediaEndedEvent, args);
1560 PlaylistRoot::BufferUnderflowHandler (MediaPlayer
*mplayer
, EventArgs
*args
)
1562 LOG_PLAYLIST ("PlaylistRoot::BufferUnderflowHandler (%p, %p)\n", mplayer
, args
);
1564 if (Surface::InMainThread ()) {
1565 EmitBufferUnderflowEvent (this);
1567 AddTickCall (EmitBufferUnderflowEvent
);
1575 PlaylistParser::PlaylistParser (PlaylistRoot
*root
, IMediaSource
*source
)
1578 this->source
= source
;
1579 this->internal
= NULL
;
1580 this->kind_stack
= NULL
;
1581 this->playlist
= NULL
;
1582 this->current_entry
= NULL
;
1583 this->current_text
= NULL
;
1584 this->error_args
= NULL
;
1588 PlaylistParser::SetSource (IMediaSource
*new_source
)
1592 source
= new_source
;
1598 PlaylistParser::Setup (XmlType type
)
1601 current_entry
= NULL
;
1602 current_text
= NULL
;
1604 was_playlist
= false;
1606 internal
= new PlaylistParserInternal ();
1607 kind_stack
= new List ();
1608 PushCurrentKind (PlaylistKind::Root
);
1610 if (type
== XML_TYPE_ASX3
) {
1611 XML_SetUserData (internal
->parser
, this);
1612 XML_SetElementHandler (internal
->parser
, on_asx_start_element
, on_asx_end_element
);
1613 XML_SetCharacterDataHandler (internal
->parser
, on_asx_text
);
1619 PlaylistParser::Cleanup ()
1622 kind_stack
->Clear (true);
1633 error_args
->unref ();
1638 PlaylistParser::~PlaylistParser ()
1644 str_match (const char *candidate
, const char *tag
)
1646 return g_ascii_strcasecmp (candidate
, tag
) == 0;
1650 PlaylistParser::on_asx_start_element (gpointer user_data
, const char *name
, const char **attrs
)
1652 ((PlaylistParser
*) user_data
)->OnASXStartElement (name
, attrs
);
1656 PlaylistParser::on_asx_end_element (gpointer user_data
, const char *name
)
1658 ((PlaylistParser
*) user_data
)->OnASXEndElement (name
);
1662 PlaylistParser::on_asx_text (gpointer user_data
, const char *data
, int len
)
1664 ((PlaylistParser
*) user_data
)->OnASXText (data
, len
);
1668 is_all_whitespace (const char *str
)
1673 for (int i
= 0; str
[i
] != 0; i
++) {
1688 // To make matters more interesting, the format of the VALUE attribute in the STARTTIME tag isn't
1689 // exactly the same as xaml's or javascript's TimeSpan format.
1691 // The time index, in hours, minutes, seconds, and hundredths of seconds.
1692 // [[hh]:mm]:ss.fract
1694 // The parser seems to stop if it finds a second dot, returnning whatever it had parsed
1697 // At most 4 digits of fract is read, the rest is ignored (even if it's not numbers).
1700 parse_int (const char **pp
, const char *end
, int *result
)
1702 const char *p
= *pp
;
1704 bool success
= false;
1706 while (p
<= end
&& g_ascii_isdigit (*p
)) {
1707 res
= res
* 10 + *p
- '0';
1720 duration_from_asx_str (PlaylistParser
*parser
, const char *str
, Duration
**res
)
1722 const char *end
= str
+ strlen (str
);
1725 int values
[] = {0, 0, 0};
1727 int hh
= 0, mm
= 0, ss
= 0;
1728 int milliseconds
= 0;
1733 if (!g_ascii_isdigit (*p
)) {
1734 parser
->ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1738 for (int i
= 0; i
< 3; i
++) {
1739 if (!parse_int (&p
, end
, &values
[i
])) {
1740 parser
->ParsingError (new ErrorEventArgs (MediaError
,
1741 MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1752 while (digits
>= 0 && g_ascii_isdigit (*p
)) {
1753 milliseconds
+= pow (10.0f
, digits
) * (*p
- '0');
1757 if (counter
== 3 && *p
!= 0 && !g_ascii_isdigit (*p
)) {
1758 parser
->ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1777 parser
->ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1781 gint64 ms
= ((hh
* 3600) + (mm
* 60) + ss
) * 1000 + milliseconds
;
1782 TimeSpan result
= TimeSpan_FromPts (MilliSeconds_ToPts (ms
));
1783 Duration
*duration
= new Duration (result
);
1791 PlaylistParser::OnASXStartElement (const char *name
, const char **attrs
)
1793 PlaylistKind::Kind kind
= StringToKind (name
);
1797 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), kind = %d\n", name
, attrs
, kind
);
1799 g_free (current_text
);
1800 current_text
= NULL
;
1802 PushCurrentKind (kind
);
1805 case PlaylistKind::Abstract
:
1806 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1807 ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1809 case PlaylistKind::Asx
:
1810 // Here the kind stack should be: Root+Asx
1811 if (kind_stack
->Length () != 2 || !AssertParentKind (PlaylistKind::Root
)) {
1812 ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
1816 playlist
= new Playlist (root
, source
);
1818 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1819 if (str_match (attrs
[i
], "VERSION")) {
1820 if (str_match (attrs
[i
+1], "3")) {
1821 playlist_version
= 3;
1822 } else if (str_match (attrs
[i
+1], "3.0")) {
1823 playlist_version
= 3;
1825 ParsingError (new ErrorEventArgs (MediaError
,
1826 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
1828 } else if (str_match (attrs
[i
], "BANNERBAR")) {
1829 ParsingError (new ErrorEventArgs (MediaError
,
1830 MoonError (MoonError::EXCEPTION
, 3007, "Unsupported ASX attribute")));
1831 } else if (str_match (attrs
[i
], "PREVIEWMODE")) {
1832 ParsingError (new ErrorEventArgs (MediaError
,
1833 MoonError (MoonError::EXCEPTION
, 3007, "Unsupported ASX attribute")));
1835 ParsingError (new ErrorEventArgs (MediaError
,
1836 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1840 case PlaylistKind::Author
:
1841 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1842 ParsingError (new ErrorEventArgs (MediaError
,
1843 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1845 case PlaylistKind::Banner
:
1846 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1847 ParsingError (new ErrorEventArgs (MediaError
,
1848 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1850 case PlaylistKind::Base
:
1851 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
1853 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1854 if (str_match (attrs
[i
], "HREF")) {
1855 // TODO: What do we do with this value?
1856 if (GetCurrentContent () != NULL
) {
1859 if (!uri
->Parse (attrs
[i
+1], true)) {
1861 } else if (uri
->GetScheme() == NULL
) {
1863 } else if (uri
->IsScheme ("http") &&
1864 uri
->IsScheme ("https") &&
1865 uri
->IsScheme ("mms") &&
1866 uri
->IsScheme ("rtsp") &&
1867 uri
->IsScheme ("rstpt")) {
1872 GetCurrentContent ()->SetBase (uri
);
1875 ParsingError (new ErrorEventArgs (MediaError
,
1876 MoonError (MoonError::EXCEPTION
, 4001, "AG_E_NETWORK_ERROR")));
1881 ParsingError (new ErrorEventArgs (MediaError
,
1882 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1887 case PlaylistKind::Copyright
:
1888 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1889 ParsingError (new ErrorEventArgs (MediaError
,
1890 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1892 case PlaylistKind::Duration
: {
1894 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1895 if (str_match (attrs
[i
], "VALUE")) {
1896 if (duration_from_asx_str (this, attrs
[i
+1], &dur
)) {
1897 if (GetCurrentEntry () != NULL
&& GetParentKind () != PlaylistKind::Ref
) {
1898 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name
, attrs
, TimeSpan_ToSecondsFloat (dur
->GetTimeSpan()));
1899 GetCurrentEntry ()->SetDuration (dur
);
1903 ParsingError (new ErrorEventArgs (MediaError
,
1904 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1910 case PlaylistKind::Entry
: {
1911 bool client_skip
= true;
1912 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1913 if (str_match (attrs
[i
], "CLIENTSKIP")) {
1914 // TODO: What do we do with this value?
1915 if (str_match (attrs
[i
+1], "YES")) {
1917 } else if (str_match (attrs
[i
+1], "NO")) {
1918 client_skip
= false;
1920 ParsingError (new ErrorEventArgs (MediaError
,
1921 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
1924 } else if (str_match (attrs
[i
], "SKIPIFREF")) {
1925 ParsingError (new ErrorEventArgs (MediaError
,
1926 MoonError (MoonError::EXCEPTION
, 3007, "Unsupported ASX attribute")));
1929 ParsingError (new ErrorEventArgs (MediaError
,
1930 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1934 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
1935 entry
->SetClientSkip (client_skip
);
1936 playlist
->AddEntry (entry
);
1937 current_entry
= entry
;
1940 case PlaylistKind::EntryRef
: {
1942 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1943 if (str_match (attrs
[i
], "HREF")) {
1945 href
= g_strdup (attrs
[i
+1]);
1946 // Docs says this attribute isn't unsupported, but an error is emitted.
1947 //} else if (str_match (attrs [i], "CLIENTBIND")) {
1948 // // TODO: What do we do with this value?
1950 ParsingError (new ErrorEventArgs (MediaError
,
1951 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1958 if (!uri
->Parse (href
)) {
1961 ParsingError (new ErrorEventArgs (MediaError
,
1962 MoonError (MoonError::EXCEPTION
, 1001, "AG_E_UNKNOWN_ERROR")));
1966 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
1968 entry
->SetSourceName (uri
);
1970 playlist
->AddEntry (entry
);
1971 current_entry
= entry
;
1974 case PlaylistKind::LogUrl
:
1975 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1976 ParsingError (new ErrorEventArgs (MediaError
,
1977 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1979 case PlaylistKind::MoreInfo
:
1980 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1981 if (str_match (attrs
[i
], "HREF")) {
1982 if (GetCurrentEntry () != NULL
)
1983 GetCurrentEntry ()->SetInfoURL (attrs
[i
+1]);
1984 } else if (str_match (attrs
[i
], "TARGET")) {
1985 if (GetCurrentEntry () != NULL
)
1986 GetCurrentEntry ()->SetInfoTarget (attrs
[i
+1]);
1988 ParsingError (new ErrorEventArgs (MediaError
,
1989 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1994 case PlaylistKind::StartTime
: {
1996 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1997 if (str_match (attrs
[i
], "VALUE")) {
1998 if (duration_from_asx_str (this, attrs
[i
+1], &dur
) && dur
->HasTimeSpan ()) {
1999 if (GetCurrentEntry () != NULL
&& GetParentKind () != PlaylistKind::Ref
) {
2000 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name
, attrs
, TimeSpan_ToSecondsFloat (dur
->GetTimeSpan()));
2001 GetCurrentEntry ()->SetStartTime (dur
->GetTimeSpan ());
2005 ParsingError (new ErrorEventArgs (MediaError
,
2006 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2013 case PlaylistKind::Ref
: {
2014 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
2015 if (str_match (attrs
[i
], "HREF")) {
2016 if (GetCurrentEntry () != NULL
&& GetCurrentEntry ()->GetSourceName () == NULL
) {
2018 if (uri
->Parse (attrs
[i
+1])) {
2019 GetCurrentEntry ()->SetSourceName (uri
);
2022 ParsingError (new ErrorEventArgs (MediaError
,
2023 MoonError (MoonError::EXCEPTION
, 1001, "AG_E_UNKNOWN_ERROR")));
2028 ParsingError (new ErrorEventArgs (MediaError
,
2029 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2035 case PlaylistKind::Param
: {
2036 const char *name
= NULL
;
2037 const char *value
= NULL
;
2039 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
2040 if (str_match (attrs
[i
], "name")) {
2041 name
= attrs
[i
+ 1];
2042 } else if (str_match (attrs
[i
], "value")) {
2043 value
= attrs
[i
+ 1];
2045 ParsingError (new ErrorEventArgs (MediaError
,
2046 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2050 if (value
!= NULL
&& value
[0] != 0 && name
!= NULL
&& name
[0] != 0) {
2051 PlaylistEntry
*entry
= GetCurrentEntry ();
2056 entry
->AddParams (name
, value
);
2062 case PlaylistKind::Title
:
2063 if (attrs
!= NULL
&& attrs
[0] != NULL
)
2064 ParsingError (new ErrorEventArgs (MediaError
,
2065 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2067 case PlaylistKind::StartMarker
:
2068 case PlaylistKind::EndMarker
:
2069 case PlaylistKind::Repeat
:
2070 case PlaylistKind::Event
:
2071 ParsingError (new ErrorEventArgs (MediaError
,
2072 MoonError (MoonError::EXCEPTION
, 3006, "Unsupported ASX element")));
2074 case PlaylistKind::Root
:
2075 case PlaylistKind::Unknown
:
2077 LOG_PLAYLIST ("PlaylistParser::OnStartElement ('%s', %p): Unknown kind: %d\n", name
, attrs
, kind
);
2078 ParsingError (new ErrorEventArgs (MediaError
,
2079 MoonError (MoonError::EXCEPTION
, 3004, "Invalid ASX element")));
2085 PlaylistParser::OnASXEndElement (const char *name
)
2087 PlaylistKind::Kind kind
= GetCurrentKind ();
2090 LOG_PLAYLIST ("PlaylistParser::OnEndElement (%s), GetCurrentKind (): %d, GetCurrentKind () to string: %s\n", name
, kind
, KindToString (kind
));
2093 case PlaylistKind::Abstract
:
2094 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2096 if (GetCurrentContent () != NULL
)
2097 GetCurrentContent ()->SetAbstract (current_text
);
2099 case PlaylistKind::Author
:
2100 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2102 if (GetCurrentContent () != NULL
)
2103 GetCurrentContent ()->SetAuthor (current_text
);
2105 case PlaylistKind::Base
:
2106 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2109 case PlaylistKind::Copyright
:
2110 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2112 if (GetCurrentContent () != NULL
)
2113 GetCurrentContent ()->SetCopyright (current_text
);
2115 case PlaylistKind::Duration
:
2116 if (!AssertParentKind (PlaylistKind::Entry
| PlaylistKind::Ref
))
2118 if (current_text
== NULL
)
2120 duration_from_asx_str (this, current_text
, &dur
);
2121 if (GetCurrentEntry () != NULL
)
2122 GetCurrentEntry ()->SetDuration (dur
);
2124 case PlaylistKind::Entry
:
2125 if (!AssertParentKind (PlaylistKind::Asx
))
2127 if (!is_all_whitespace (current_text
)) {
2128 ParsingError (new ErrorEventArgs (MediaError
,
2129 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2132 case PlaylistKind::EntryRef
:
2133 if (!AssertParentKind (PlaylistKind::Asx
))
2136 case PlaylistKind::StartTime
:
2137 if (!AssertParentKind (PlaylistKind::Entry
| PlaylistKind::Ref
))
2139 if (!is_all_whitespace (current_text
)) {
2140 ParsingError (new ErrorEventArgs (MediaError
,
2141 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2144 case PlaylistKind::Title
:
2145 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2147 if (GetCurrentContent () != NULL
)
2148 GetCurrentContent ()->SetTitle (current_text
);
2150 case PlaylistKind::Asx
:
2151 if (playlist_version
== 3)
2152 was_playlist
= true;
2153 if (!AssertParentKind (PlaylistKind::Root
))
2156 case PlaylistKind::Ref
:
2157 if (!AssertParentKind (PlaylistKind::Entry
))
2159 if (!is_all_whitespace (current_text
)) {
2160 ParsingError (new ErrorEventArgs (MediaError
,
2161 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2164 case PlaylistKind::MoreInfo
:
2165 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2167 if (!is_all_whitespace (current_text
)) {
2168 ParsingError (new ErrorEventArgs (MediaError
,
2169 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2172 case PlaylistKind::Param
:
2173 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2175 if (!is_all_whitespace (current_text
)) {
2176 ParsingError (new ErrorEventArgs (MediaError
,
2177 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2181 LOG_PLAYLIST ("PlaylistParser::OnEndElement ('%s'): Unknown kind %d.\n", name
, kind
);
2182 ParsingError (new ErrorEventArgs (MediaError
,
2183 MoonError (MoonError::EXCEPTION
, 3004, "Invalid ASX element")));
2187 if (current_text
!= NULL
) {
2188 g_free (current_text
);
2189 current_text
= NULL
;
2192 switch (GetCurrentKind ()) {
2193 case PlaylistKind::Entry
:
2203 PlaylistParser::OnASXText (const char *text
, int len
)
2205 char *a
= g_strndup (text
, len
);
2208 char *p
= g_strndup (text
, len
);
2209 for (int i
= 0; p
[i
] != 0; i
++)
2210 if (p
[i
] == 10 || p
[i
] == 13)
2213 LOG_PLAYLIST ("PlaylistParser::OnText (%s, %d)\n", p
, len
);
2217 if (current_text
== NULL
) {
2220 char *b
= g_strconcat (current_text
, a
, NULL
);
2221 g_free (current_text
);
2227 PlaylistParser::Is (IMediaSource
*source
, const char *asx_header
)
2229 bool result
= false;
2230 int asx_header_length
= strlen (asx_header
);
2231 unsigned char buffer
[20];
2234 result
= source
->Peek ((guint8
*) buffer
, asx_header_length
);
2238 // skip any whitespace
2239 unsigned char c
= buffer
[0];
2245 result
= source
->ReadAll ((guint8
*) buffer
, 1);
2251 if (buffer
[1] == 0xbb && buffer
[2] == 0xbf) { // UTF-8 BOM: EF BB BF
2252 result
= source
->ReadAll ((guint8
*) buffer
, 3);
2257 // TODO: there might be other BOMs we should handle too
2261 result
= !g_ascii_strncasecmp ((const char *) buffer
, asx_header
, asx_header_length
);
2268 source
->Seek (0, SEEK_SET
);
2274 PlaylistParser::IsASX3 (IMediaSource
*source
)
2276 return Is (source
, "<ASX");
2280 PlaylistParser::IsASX2 (IMediaSource
*source
)
2282 return Is (source
, "[Reference]");
2286 PlaylistParser::ParseASX2 ()
2288 const int BUFFER_SIZE
= 1024;
2290 char buffer
[BUFFER_SIZE
];
2296 playlist_version
= 2;
2298 bytes_read
= source
->ReadSome (buffer
, BUFFER_SIZE
);
2299 if (bytes_read
< 0) {
2300 LOG_PLAYLIST_WARN ("Could not read asx document for parsing.\n");
2304 key_file
= g_key_file_new ();
2305 if (!g_key_file_load_from_data (key_file
, buffer
, bytes_read
,
2306 G_KEY_FILE_NONE
, NULL
)) {
2307 LOG_PLAYLIST_WARN ("Invalid asx2 document.\n");
2308 g_key_file_free (key_file
);
2312 ref
= g_key_file_get_value (key_file
, "Reference", "Ref1", NULL
);
2314 LOG_PLAYLIST_WARN ("Could not find Ref1 entry in asx2 document.\n");
2315 g_key_file_free (key_file
);
2319 if (!g_str_has_prefix (ref
, "http://") || !g_str_has_suffix (ref
, "MSWMExt=.asf")) {
2320 LOG_PLAYLIST_WARN ("Could not find a valid uri within Ref1 entry in asx2 document.\n");
2322 g_key_file_free (key_file
);
2326 mms_uri
= g_strdup_printf ("mms://%s", strstr (ref
, "http://") + strlen ("http://"));
2328 g_key_file_free (key_file
);
2331 playlist
= new Playlist (root
, source
);
2333 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
2335 if (uri
->Parse (mms_uri
)) {
2336 entry
->SetSourceName (uri
);
2340 playlist
->AddEntry (entry
);
2341 current_entry
= entry
;
2347 PlaylistParser::TryFixError (gint8
*current_buffer
, int bytes_read
)
2351 if (XML_GetErrorCode (internal
->parser
) != XML_ERROR_INVALID_TOKEN
)
2354 int index
= XML_GetErrorByteIndex (internal
->parser
);
2356 if (index
> bytes_read
)
2359 LOG_PLAYLIST ("Attempting to fix invalid token error index: %d\n", index
);
2361 // OK, so we are going to guess that we are in an attribute here and walk back
2362 // until we hit a control char that should be escaped.
2363 char * escape
= NULL
;
2364 while (index
>= 0) {
2365 switch (current_buffer
[index
]) {
2367 escape
= g_strdup ("&");
2370 escape
= g_strdup ("<");
2373 escape
= g_strdup (">");
2384 LOG_PLAYLIST_WARN ("Unable to find an invalid escape character to fix in ASX: %s.\n", current_buffer
);
2389 int escape_len
= strlen (escape
);
2390 int new_size
= source
->GetSize () + escape_len
- 1;
2391 int patched_size
= internal
->bytes_read
+ bytes_read
+ escape_len
- 1;
2392 gint8
* new_buffer
= (gint8
*) g_malloc (new_size
);
2394 source
->Seek (0, SEEK_SET
);
2395 source
->ReadSome (new_buffer
, internal
->bytes_read
);
2397 memcpy (new_buffer
+ internal
->bytes_read
, current_buffer
, index
);
2398 memcpy (new_buffer
+ internal
->bytes_read
+ index
, escape
, escape_len
);
2399 memcpy (new_buffer
+ internal
->bytes_read
+ index
+ escape_len
, current_buffer
+ index
+ 1, bytes_read
- index
- 1);
2401 source
->Seek (internal
->bytes_read
+ bytes_read
, SEEK_SET
);
2402 source
->ReadSome (new_buffer
+ patched_size
, new_size
- patched_size
);
2404 media
= source
->GetMediaReffed ();
2406 MemorySource
*reparse_source
= new MemorySource (media
, new_buffer
, new_size
);
2407 SetSource (reparse_source
);
2408 reparse_source
->unref ();
2410 internal
->reparse
= true;
2421 PlaylistParser::Parse ()
2424 gint64 last_available_pos
;
2427 LOG_PLAYLIST ("PlaylistParser::Parse ()\n");
2430 // Don't try to parse anything until we have all the data.
2431 if (internal
!= NULL
)
2432 internal
->reparse
= false;
2433 size
= source
->GetSize ();
2434 last_available_pos
= source
->GetLastAvailablePosition ();
2435 if (size
!= -1 && last_available_pos
!= -1 && size
!= last_available_pos
)
2436 return MEDIA_NOT_ENOUGH_DATA
;
2438 if (this->IsASX2 (source
)) {
2439 /* Parse as a asx2 mms file */
2440 Setup (XML_TYPE_NONE
);
2441 result
= this->ParseASX2 ();
2442 } else if (this->IsASX3 (source
)) {
2443 Setup (XML_TYPE_ASX3
);
2444 result
= this->ParseASX3 ();
2448 } while (result
&& internal
->reparse
);
2450 return result
? MEDIA_SUCCESS
: MEDIA_FAIL
;
2454 PlaylistParser::ParseASX3 ()
2459 // asx documents don't tend to be very big, so there's no need for a big buffer
2460 const unsigned int BUFFER_SIZE
= 1024;
2463 buffer
= XML_GetBuffer(internal
->parser
, BUFFER_SIZE
);
2464 if (buffer
== NULL
) {
2465 fprintf (stderr
, "Could not allocate memory for asx document parsing.\n");
2469 bytes_read
= source
->ReadSome (buffer
, BUFFER_SIZE
/ 2);
2470 if (bytes_read
< 0) {
2471 fprintf (stderr
, "Could not read asx document for parsing.\n");
2475 if (bytes_read
> 0) {
2477 * ASX files are ANSI (actually whatever encoding is in use on the machine that generated the ASX file...)
2478 * try to convert from ANSI (windows 1252 codepage) to utf8
2482 gsize bytes_converted
;
2483 gsize bytes_written
;
2485 char *utf8
= g_convert ((char *) buffer
, bytes_read
, "UTF-8", "CP1252", &bytes_converted
, &bytes_written
, &err
);
2488 if (bytes_written
>= BUFFER_SIZE
) {
2489 fprintf (stderr
, "Moonlight: Conversion from ANSI playlist file to UTF8 to more than doubled the required space, this is not normal (file will not be parsed).\n");
2492 memcpy (buffer
, utf8
, bytes_written
);
2493 bytes_read
= bytes_written
;
2497 fprintf (stderr
, "Moonlight: Could not convert ASX3 playlist file from ANSI encoding to UTF8. Will try to parse ASX3 playlist as if it was UTF8.\n");
2501 * do & => & fixups
2504 int size_left
= BUFFER_SIZE
- bytes_read
;
2506 char *cbuf
= (char *) buffer
;
2507 while (size_left
> 4 && pos
< bytes_read
) {
2508 if (cbuf
[pos
] == '&') {
2509 memmove (cbuf
+ 5 + pos
, cbuf
+ 1 + pos
, bytes_read
- pos
+ 1);
2510 cbuf
[pos
+ 1] = 'a';
2511 cbuf
[pos
+ 2] = 'm';
2512 cbuf
[pos
+ 3] = 'p';
2513 cbuf
[pos
+ 4] = ';';
2523 if (!XML_ParseBuffer (internal
->parser
, bytes_read
, bytes_read
== 0)) {
2524 if (error_args
!= NULL
)
2527 switch (XML_GetErrorCode (internal
->parser
)) {
2528 case XML_ERROR_NO_ELEMENTS
:
2529 ParsingError (new ErrorEventArgs (MediaError
,
2530 MoonError (MoonError::EXCEPTION
, 7000, "unexpected end of input")));
2532 case XML_ERROR_DUPLICATE_ATTRIBUTE
:
2533 ParsingError (new ErrorEventArgs (MediaError
,
2534 MoonError (MoonError::EXCEPTION
, 7031, "wfc: unique attribute spec")));
2536 case XML_ERROR_INVALID_TOKEN
:
2537 // save error args in case the error fixing fails (in which case we want this error, not the error the error fixing caused)
2538 error_args
= new ErrorEventArgs (MediaError
,
2539 MoonError (MoonError::EXCEPTION
, 7007, "quote expected"));
2540 if (TryFixError ((gint8
*) buffer
, bytes_read
))
2544 char *msg
= g_strdup_printf ("%s %d (%d, %d)",
2545 XML_ErrorString (XML_GetErrorCode (internal
->parser
)), (int) XML_GetErrorCode (internal
->parser
),
2546 (int) XML_GetCurrentLineNumber (internal
->parser
), (int) XML_GetCurrentColumnNumber (internal
->parser
));
2547 ParsingError (new ErrorEventArgs (MediaError
,
2548 MoonError (MoonError::EXCEPTION
, 3000, msg
)));
2554 if (bytes_read
== 0)
2557 internal
->bytes_read
+= bytes_read
;
2560 return playlist
!= NULL
;
2564 PlaylistParser::GetCurrentContent ()
2566 if (current_entry
!= NULL
)
2567 return current_entry
;
2573 PlaylistParser::GetCurrentEntry ()
2575 return current_entry
;
2579 PlaylistParser::EndEntry ()
2581 this->current_entry
= NULL
;
2585 PlaylistParser::PushCurrentKind (PlaylistKind::Kind kind
)
2587 kind_stack
->Append (new KindNode (kind
));
2588 LOG_PLAYLIST ("PlaylistParser::Push (%d)\n", kind
);
2592 PlaylistParser::PopCurrentKind ()
2594 LOG_PLAYLIST ("PlaylistParser::PopCurrentKind (), current: %d\n", ((KindNode
*)kind_stack
->Last ())->kind
);
2595 kind_stack
->Remove (kind_stack
->Last ());
2599 PlaylistParser::GetCurrentKind ()
2601 KindNode
*node
= (KindNode
*) kind_stack
->Last ();
2606 PlaylistParser::GetParentKind ()
2608 KindNode
*node
= (KindNode
*) kind_stack
->Last ()->prev
;
2613 PlaylistParser::AssertParentKind (int kind
)
2615 LOG_PLAYLIST ("PlaylistParser::AssertParentKind (%d), GetParentKind: %d, result: %d\n", kind
, GetParentKind (), GetParentKind () & kind
);
2617 if (GetParentKind () & kind
)
2620 ParsingError (new ErrorEventArgs (MediaError
,
2621 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2627 PlaylistParser::ParsingError (ErrorEventArgs
*args
)
2629 LOG_PLAYLIST ("PlaylistParser::ParsingError (%s)\n", args
->GetErrorMessage());
2631 XML_StopParser (internal
->parser
, false);
2635 return; // don't overwrite any previous errors.
2637 error_args
= args
; // don't ref, this method is called like this: ParsingError (new ErrorEventArgs (...));, so the caller gives us the ref he has
2641 PlaylistKind
PlaylistParser::playlist_kinds
[] = {
2643 PlaylistKind ("ABSTRACT", PlaylistKind::Abstract
),
2644 PlaylistKind ("ASX", PlaylistKind::Asx
),
2645 PlaylistKind ("ROOT", PlaylistKind::Root
),
2646 PlaylistKind ("AUTHOR", PlaylistKind::Author
),
2647 PlaylistKind ("BANNER", PlaylistKind::Banner
),
2648 PlaylistKind ("BASE", PlaylistKind::Base
),
2649 PlaylistKind ("COPYRIGHT", PlaylistKind::Copyright
),
2650 PlaylistKind ("DURATION", PlaylistKind::Duration
),
2651 PlaylistKind ("ENTRY", PlaylistKind::Entry
),
2652 PlaylistKind ("ENTRYREF", PlaylistKind::EntryRef
),
2653 PlaylistKind ("LOGURL", PlaylistKind::LogUrl
),
2654 PlaylistKind ("MOREINFO", PlaylistKind::MoreInfo
),
2655 PlaylistKind ("REF", PlaylistKind::Ref
),
2656 PlaylistKind ("STARTTIME", PlaylistKind::StartTime
),
2657 PlaylistKind ("TITLE", PlaylistKind::Title
),
2658 PlaylistKind ("STARTMARKER", PlaylistKind::StartMarker
),
2659 PlaylistKind ("REPEAT", PlaylistKind::Repeat
),
2660 PlaylistKind ("ENDMARKER", PlaylistKind::EndMarker
),
2661 PlaylistKind ("PARAM", PlaylistKind::Param
),
2662 PlaylistKind ("EVENT", PlaylistKind::Event
),
2664 PlaylistKind (NULL
, PlaylistKind::Unknown
)
2668 PlaylistParser::StringToKind (const char *str
)
2670 PlaylistKind::Kind kind
= PlaylistKind::Unknown
;
2672 for (int i
= 0; playlist_kinds
[i
].str
!= NULL
; i
++) {
2673 if (str_match (str
, playlist_kinds
[i
].str
)) {
2674 kind
= playlist_kinds
[i
].kind
;
2679 LOG_PLAYLIST ("PlaylistParser::StringToKind ('%s') = %d\n", str
, kind
);
2685 PlaylistParser::KindToString (PlaylistKind::Kind kind
)
2687 const char *result
= NULL
;
2689 for (int i
= 0; playlist_kinds
[i
].str
!= NULL
; i
++) {
2690 if (playlist_kinds
[i
].kind
== kind
) {
2691 result
= playlist_kinds
[i
].str
;
2696 LOG_PLAYLIST ("PlaylistParser::KindToString (%d) = '%s'\n", kind
, result
);