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
= NULL
;
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
->GetDemuxerReffed ();
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 if (playlist
== NULL
|| parent
== NULL
) {
331 parent
->ReplaceCurrentEntry (playlist
);
334 if (parent
->GetCurrentEntry () == this) {
337 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p): opened entry in advance, waiting for current entry to finish.\n", media
, args
);
347 PlaylistEntry::SeekingHandler (Media
*media
, EventArgs
*args
)
349 PlaylistRoot
*root
= GetRoot ();
351 LOG_PLAYLIST ("PlaylistEntry::SeekingHandler (%p, %p)\n", media
, args
);
353 g_return_if_fail (root
!= NULL
);
357 root
->Emit (PlaylistRoot::SeekingEvent
, args
);
361 PlaylistEntry::SeekCompletedHandler (Media
*media
, EventArgs
*args
)
363 PlaylistRoot
*root
= GetRoot ();
365 LOG_PLAYLIST ("PlaylistEntry::SeekCompletedHandler (%p, %p)\n", media
, args
);
367 g_return_if_fail (root
!= NULL
);
371 root
->Emit (PlaylistRoot::SeekCompletedEvent
, args
);
375 PlaylistEntry::CurrentStateChangedHandler (Media
*media
, EventArgs
*args
)
377 LOG_PLAYLIST ("PlaylistEntry::CurrentStateChangedHandler (%p, %p)\n", media
, args
);
381 PlaylistEntry::MediaErrorHandler (Media
*media
, ErrorEventArgs
*args
)
383 LOG_PLAYLIST ("PlaylistEntry::MediaErrorHandler (%p, %p): %s '%s'\n", media
, args
, GetFullSourceName (), args
? args
->GetErrorMessage() : "?");
385 g_return_if_fail (parent
!= NULL
);
387 parent
->OnEntryFailed (args
);
391 PlaylistEntry::DownloadProgressChangedHandler (Media
*media
, EventArgs
*args
)
395 LOG_PLAYLIST ("PlaylistEntry::DownloadProgressChanged (%p, %p %.2f). Disposed: %i\n", media
, args
, args
? ((ProgressEventArgs
*) args
)->progress
: -1.0, IsDisposed ());
402 g_return_if_fail (root
!= NULL
);
406 root
->Emit (PlaylistRoot::DownloadProgressChangedEvent
, args
);
410 PlaylistEntry::BufferingProgressChangedHandler (Media
*media
, EventArgs
*args
)
412 PlaylistRoot
*root
= GetRoot ();
414 LOG_PLAYLIST ("PlaylistEntry::BufferingProgressChanged (%p, %p) %.2f\n", media
, args
, args
? ((ProgressEventArgs
*) args
)->progress
: -1.0);
417 return; // this might happen if the media is still buffering and we're in the process of getting cleaned up
421 root
->Emit (PlaylistRoot::BufferingProgressChangedEvent
, args
);
425 PlaylistEntry::Seek (guint64 pts
)
427 LOG_PLAYLIST ("PlaylistEntry::Seek (%" G_GUINT64_FORMAT
")\n", pts
);
429 g_return_if_fail (media
!= NULL
);
431 media
->SeekAsync (pts
);
435 PlaylistEntry::AddParams (const char *name
, const char *value
)
437 char *uppername
= g_ascii_strup (name
, strlen (name
));
438 if (!strcmp (uppername
, "AUTHOR")) {
440 } else if (!strcmp (uppername
, "ABSTRACT")) {
442 } else if (!strcmp (uppername
, "TITLE")) {
444 } else if (!strcmp (uppername
, "COPYRIGHT")) {
445 SetCopyright (value
);
446 } else if (!strcmp (uppername
, "INFOTARGET")) {
447 SetInfoTarget (value
);
448 } else if (!strcmp (uppername
, "INFOURL")) {
452 params
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, g_free
);
454 if (g_hash_table_lookup (params
, uppername
) == NULL
) {
455 g_hash_table_insert (params
, uppername
, g_strdup (value
));
463 PlaylistEntry::GetBase ()
469 PlaylistEntry::GetBaseInherited ()
474 return parent
->GetBaseInherited ();
479 PlaylistEntry::SetBase (Uri
*base
)
481 // TODO: Haven't been able to make BASE work with SL,
482 // which means that I haven't been able to confirm any behaviour.
483 if (!(set_values
& PlaylistKind::Base
)) {
485 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Base
);
492 PlaylistEntry::GetTitle ()
498 PlaylistEntry::SetTitle (const char *title
)
500 if (!(set_values
& PlaylistKind::Title
)) {
501 this->title
= g_strdup (title
);
502 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Title
);
507 PlaylistEntry::GetAuthor ()
512 void PlaylistEntry::SetAuthor (const char *author
)
514 if (!(set_values
& PlaylistKind::Author
)) {
515 this->author
= g_strdup (author
);
516 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Author
);
521 PlaylistEntry::GetAbstract ()
527 PlaylistEntry::SetAbstract (const char *abstract
)
529 if (!(set_values
& PlaylistKind::Abstract
)) {
530 this->abstract
= g_strdup (abstract
);
531 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Abstract
);
536 PlaylistEntry::GetCopyright ()
542 PlaylistEntry::SetCopyright (const char *copyright
)
544 if (!(set_values
& PlaylistKind::Copyright
)) {
545 this->copyright
= g_strdup (copyright
);
546 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Copyright
);
551 PlaylistEntry::GetSourceName ()
557 PlaylistEntry::SetSourceName (Uri
*source_name
)
559 if (this->source_name
)
560 delete this->source_name
;
561 this->source_name
= source_name
;
565 PlaylistEntry::GetStartTime ()
571 PlaylistEntry::SetStartTime (TimeSpan start_time
)
573 if (!(set_values
& PlaylistKind::StartTime
)) {
574 this->start_time
= start_time
;
575 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::StartTime
);
580 PlaylistEntry::GetDuration ()
586 PlaylistEntry::GetInheritedDuration ()
588 if (HasDuration ()) {
589 return GetDuration ();
590 } else if (parent
!= NULL
) {
591 return parent
->GetInheritedDuration ();
598 PlaylistEntry::HasInheritedDuration ()
600 if (HasDuration ()) {
602 } else if (parent
!= NULL
) {
603 return parent
->HasInheritedDuration ();
610 PlaylistEntry::SetDuration (Duration
*duration
)
612 if (!(set_values
& PlaylistKind::Duration
)) {
613 this->duration
= duration
;
614 set_values
= (PlaylistKind::Kind
) (set_values
| PlaylistKind::Duration
);
619 PlaylistEntry::GetInfoTarget ()
625 PlaylistEntry::SetInfoTarget (const char *info_target
)
627 g_free (this->info_target
);
628 this->info_target
= g_strdup (info_target
);
632 PlaylistEntry::GetInfoURL ()
638 PlaylistEntry::SetInfoURL (const char *info_url
)
640 g_free (this->info_url
);
641 this->info_url
= g_strdup (info_url
);
645 PlaylistEntry::GetClientSkip ()
651 PlaylistEntry::SetClientSkip (bool value
)
657 PlaylistEntry::GetElement ()
659 g_return_val_if_fail (parent
!= NULL
, NULL
);
661 return parent
->GetElement ();
665 PlaylistEntry::ClearMedia ()
667 g_return_if_fail (media
!= NULL
);
673 PlaylistEntry::GetMediaPlayer ()
675 PlaylistRoot
*root
= GetRoot ();
677 g_return_val_if_fail (root
!= NULL
, NULL
);
679 return root
->GetMediaPlayer ();
683 add_attribute (MediaAttributeCollection
*attributes
, const char *name
, const char *attr
)
688 MediaAttribute
*attribute
= new MediaAttribute ();
689 attribute
->SetValue (attr
);
690 attribute
->SetName (name
);
692 attributes
->Add (attribute
);
697 add_attribute_glib (const char *name
, const char *value
, MediaAttributeCollection
*attributes
)
699 add_attribute (attributes
, name
, value
);
703 PlaylistEntry::PopulateMediaAttributes ()
705 LOG_PLAYLIST ("PlaylistEntry::PopulateMediaAttributes ()\n");
707 const char *abstract
= NULL
;
708 const char *author
= NULL
;
709 const char *copyright
= NULL
;
710 const char *title
= NULL
;
711 const char *infotarget
= NULL
;
712 const char *infourl
= NULL
;
713 const char *baseurl
= NULL
;
715 MediaElement
*element
= GetElement ();
716 PlaylistEntry
*current
= this;
717 MediaAttributeCollection
*attributes
;
719 g_return_if_fail (element
!= NULL
);
721 if (!(attributes
= element
->GetAttributes ())) {
722 attributes
= new MediaAttributeCollection ();
723 element
->SetAttributes (attributes
);
725 attributes
->Clear ();
728 while (current
!= NULL
) {
729 if (abstract
== NULL
)
730 abstract
= current
->GetAbstract ();
732 author
= current
->GetAuthor ();
733 if (copyright
== NULL
)
734 copyright
= current
->GetCopyright ();
736 title
= current
->GetTitle ();
737 if (infotarget
== NULL
)
738 infotarget
= current
->GetInfoTarget ();
740 infourl
= current
->GetInfoURL ();
741 if (baseurl
== NULL
&& current
->GetBase () != NULL
)
742 baseurl
= current
->GetBase ()->originalString
;
744 current
= current
->GetParent ();
747 add_attribute (attributes
, "ABSTRACT", abstract
);
748 add_attribute (attributes
, "AUTHOR", author
);
749 add_attribute (attributes
, "BaseURL", baseurl
);
750 add_attribute (attributes
, "COPYRIGHT", copyright
);
751 add_attribute (attributes
, "InfoTarget", infotarget
);
752 add_attribute (attributes
, "InfoURL", infourl
);
753 add_attribute (attributes
, "TITLE", title
);
756 while (current
!= NULL
) {
757 if (current
->params
!= NULL
)
758 g_hash_table_foreach (current
->params
, (GHFunc
) add_attribute_glib
, attributes
);
759 current
= current
->GetParent ();
764 PlaylistEntry::GetFullSourceName ()
767 * Now here we have some interesting semantics:
768 * - BASE has to be a complete url, with scheme and domain
769 * - BASE only matters up to the latest / (if no /, the entire BASE is used)
771 * Examples (numbered according to the test-playlist-with-base test in test/media/video)
773 * 01 localhost/dir/ + * = error
774 * 02 /dir/ + * = error
776 * 04 http://localhost/dir/ + somefile = http://localhost/dir/somefile
777 * 05 http://localhost/dir + somefile = http://localhost/somefile
778 * 06 http://localhost + somefile = http://localhost/somefile
779 * 07 http://localhost/dir/ + /somefile = http://localhost/somefile
780 * 08 http://localhost/dir/ + dir2/somefile = http://localhost/dir/dir2/somefile
781 * 09 rtsp://localhost/ + somefile = http://localhost/somefile
782 * 10 mms://localhost/dir/ + somefile = mms://localhost/dir/somefile
783 * 11 http://localhost/?huh + somefile = http://localhost/somefile
784 * 12 http://localhost/#huh + somefile = http://localhost/somefile
785 * 13 httP://localhost/ + somefile = http://localhost/somefile
789 // TODO: url validation, however it should probably happen inside MediaElement when we set the source
791 if (full_source_name
== NULL
) {
792 Uri
*base
= GetBaseInherited ();
793 Uri
*current
= GetSourceName ();
798 //printf ("PlaylistEntry::GetFullSourceName (), base: %s, current: %s\n", base ? base->ToString () : "NULL", current ? current->ToString () : "NULL");
800 if (current
== NULL
) {
802 } else if (current
->GetHost () != NULL
) {
803 //printf (" current host (%s) is something, scheme: %s\n", current->GetHost (), current->scheme);
805 } else if (base
!= NULL
) {
807 result
->scheme
= g_strdup (base
->GetScheme());
808 result
->user
= g_strdup (base
->GetUser());
809 result
->passwd
= g_strdup (base
->GetPasswd());
810 result
->host
= g_strdup (base
->GetHost());
811 result
->port
= base
->GetPort();
812 // we ignore the params, query and fragment values.
813 if (current
->GetPath() != NULL
&& current
->GetPath() [0] == '/') {
814 //printf (" current path is relative to root dir on host\n");
815 result
->path
= g_strdup (current
->GetPath());
816 } else if (base
->GetPath() == NULL
) {
817 //printf (" base path is root dir on host\n");
818 result
->path
= g_strdup (current
->GetPath());
820 pathsep
= strrchr (base
->GetPath(), '/');
821 if (pathsep
!= NULL
) {
822 if ((size_t) (pathsep
- base
->GetPath() + 1) == strlen (base
->GetPath())) {
823 //printf (" last character of base path (%s) is /\n", base->path);
824 result
->path
= g_strjoin (NULL
, base
->GetPath(), current
->GetPath(), NULL
);
826 //printf (" base path (%s) does not end with /, only copy path up to the last /\n", base->path);
827 base_path
= g_strndup (base
->GetPath(), pathsep
- base
->GetPath() + 1);
828 result
->path
= g_strjoin (NULL
, base_path
, current
->GetPath(), NULL
);
832 //printf (" base path (%s) does not contain a /\n", base->path);
833 result
->path
= g_strjoin (NULL
, base
->GetPath(), "/", current
->GetPath(), NULL
);
837 //printf (" there's no base\n");
841 full_source_name
= result
->ToString ();
843 //printf (" result: %s\n", full_source_name);
845 if (result
!= base
&& result
!= current
)
848 return full_source_name
;
852 PlaylistEntry::Open ()
854 LOG_PLAYLIST ("PlaylistEntry::Open (), media = %p, FullSourceName = %s\n", media
, GetFullSourceName ());
857 g_return_if_fail (GetFullSourceName () != NULL
);
858 InitializeWithUri (GetFullSourceName ());
867 PlaylistEntry::Play ()
869 MediaPlayer
*mplayer
= GetMediaPlayer ();
870 PlaylistRoot
*root
= GetRoot ();
872 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");
874 g_return_if_fail (media
!= NULL
);
875 g_return_if_fail (mplayer
!= NULL
);
876 g_return_if_fail (root
!= NULL
);
881 root
->Emit (PlaylistRoot::PlayEvent
);
885 PlaylistEntry::Pause ()
887 MediaPlayer
*mplayer
= GetMediaPlayer ();
888 PlaylistRoot
*root
= GetRoot ();
890 LOG_PLAYLIST ("PlaylistEntry::Pause ()\n");
892 g_return_if_fail (media
!= NULL
);
893 g_return_if_fail (mplayer
!= NULL
);
894 g_return_if_fail (root
!= NULL
);
896 play_when_available
= false;
897 media
->PauseAsync ();
900 root
->Emit (PlaylistRoot::PauseEvent
);
904 PlaylistEntry::Stop ()
906 LOG_PLAYLIST ("PlaylistEntry::Stop ()\n");
908 play_when_available
= false;
914 PlaylistEntry::GetMedia ()
920 PlaylistEntry::IsSingleFile ()
922 return parent
? parent
->IsSingleFile () : false;
926 PlaylistEntry::GetRoot ()
933 if (parent
== NULL
) {
934 g_return_val_if_fail (GetObjectType () == Type::PLAYLISTROOT
, NULL
);
935 return (PlaylistRoot
*) this;
940 while (pl
->parent
!= NULL
)
943 g_return_val_if_fail (pl
->GetObjectType () == Type::PLAYLISTROOT
, NULL
);
945 return (PlaylistRoot
*) pl
;
952 Playlist::Playlist (Playlist
*parent
, IMediaSource
*source
)
953 : PlaylistEntry (Type::PLAYLIST
, parent
)
955 is_single_file
= false;
959 this->source
= source
;
960 this->source
->ref ();
963 Playlist::Playlist (Type::Kind kind
)
964 : PlaylistEntry (kind
)
966 LOG_PLAYLIST ("Playlist::Playlist ()\n");
967 is_single_file
= true;
970 AddEntry (new PlaylistEntry (this));
976 LOG_PLAYLIST ("Playlist::Init ()\n");
978 entries
= new List ();
987 PlaylistEntry
*entry
;
989 LOG_PLAYLIST ("Playlist::Dispose () id: %i\n", GET_OBJ_ID (this));
993 if (entries
!= NULL
) {
994 node
= (PlaylistNode
*) entries
->First ();
995 while (node
!= NULL
) {
996 entry
= node
->GetEntry ();
999 node
= (PlaylistNode
*) node
->next
;
1010 PlaylistEntry::Dispose ();
1015 Playlist::IsCurrentEntryLastEntry ()
1017 PlaylistEntry
*entry
;
1020 if (entries
->Last () == NULL
)
1023 if (current_node
!= entries
->Last ())
1026 entry
= GetCurrentEntry ();
1028 if (!entry
->IsPlaylist ())
1031 pl
= (Playlist
*) entry
;
1033 return pl
->IsCurrentEntryLastEntry ();
1039 PlaylistEntry
*current_entry
;
1041 LOG_PLAYLIST ("Playlist::Open ()\n");
1043 current_node
= (PlaylistNode
*) entries
->First ();
1045 current_entry
= GetCurrentEntry ();
1047 while (current_entry
&& current_entry
->HasDuration () && current_entry
->GetDuration ()->HasTimeSpan() &&
1048 current_entry
->GetDuration ()->GetTimeSpan () == 0) {
1049 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry
->GetSourceName ()->ToString ());
1050 current_node
= (PlaylistNode
*) current_node
->next
;
1051 current_entry
= GetCurrentEntry ();
1055 current_entry
->Open ();
1059 LOG_PLAYLIST ("Playlist::Open (): current node: %p, current entry: %p\n", current_entry
, GetCurrentEntry ());
1063 Playlist::PlayNext ()
1065 PlaylistEntry
*current_entry
;
1066 MediaElement
*element
= GetElement ();
1067 PlaylistRoot
*root
= GetRoot ();
1069 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p\n", current_node
);
1070 g_return_val_if_fail (root
!= NULL
, false);
1077 current_entry
= GetCurrentEntry ();
1079 if (current_entry
->HasDuration() && current_entry
->GetDuration()->IsForever ()) {
1080 element
->SetPlayRequested ();
1081 current_entry
->Play ();
1085 if (current_entry
->IsPlaylist ()) {
1086 Playlist
*current_playlist
= (Playlist
*) current_entry
;
1087 if (current_playlist
->PlayNext ())
1091 if (current_node
->next
) {
1092 current_node
= (PlaylistNode
*) current_node
->next
;
1094 current_entry
= GetCurrentEntry ();
1095 if (current_entry
) {
1096 LOG_PLAYLIST ("Playlist::PlayNext () playing entry: %p %s\n", current_entry
, current_entry
->GetFullSourceName ());
1097 element
->SetPlayRequested ();
1098 root
->Emit (PlaylistRoot::EntryChangedEvent
);
1099 current_entry
->Open ();
1104 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p, nothing to play (is root: %i)\n", current_node
, GetObjectType () == Type::PLAYLISTROOT
);
1106 if (GetObjectType () == Type::PLAYLISTROOT
)
1107 root
->Emit (PlaylistRoot::MediaEndedEvent
);
1113 Playlist::OnEntryEnded ()
1115 LOG_PLAYLIST ("Playlist::OnEntryEnded ()\n");
1120 Playlist::OnEntryFailed (ErrorEventArgs
*args
)
1123 PlaylistRoot
*root
= GetRoot ();
1125 LOG_PLAYLIST ("Playlist::OnEntryFailed () extended_code: %i is_single_file: %i\n", args
? args
->GetExtendedCode() : 0, is_single_file
);
1127 g_return_if_fail (root
!= NULL
);
1129 // media or playlist 404: fatal
1130 // invalid playlist (playlist parsing failed): fatal
1131 // invalid media (gif, swf): play next
1135 // check if we're in a playlist
1136 IMediaDemuxer
*demuxer
= NULL
;
1137 if (GetMedia () != NULL
)
1138 demuxer
= GetMedia ()->GetDemuxerReffed ();
1140 if (demuxer
!= NULL
&& demuxer
->GetObjectType () == Type::ASXDEMUXER
) {
1142 if (args
->GetExtendedCode() == MEDIA_UNKNOWN_CODEC
) {
1148 // we're not a playlist
1159 root
->Emit (PlaylistRoot::MediaErrorEvent
, args
);
1166 Playlist::Seek (guint64 pts
)
1168 PlaylistEntry
*current_entry
;
1170 LOG_PLAYLIST ("Playlist::Seek (%" G_GUINT64_FORMAT
")\n", pts
);
1172 current_entry
= GetCurrentEntry ();
1174 g_return_if_fail (current_entry
!= NULL
);
1176 current_entry
->Seek (pts
);
1182 PlaylistEntry
*current_entry
;
1184 LOG_PLAYLIST ("Playlist::Play ()\n");
1186 current_entry
= GetCurrentEntry ();
1188 g_return_if_fail (current_entry
!= NULL
);
1190 if (current_entry
&& current_entry
->HasDuration () && current_entry
->GetDuration () == 0) {
1191 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry
->GetSourceName ()->ToString ());
1195 current_entry
->Play ();
1202 PlaylistEntry
*current_entry
;
1204 LOG_PLAYLIST ("Playlist::Pause ()\n");
1206 current_entry
= GetCurrentEntry ();
1208 g_return_if_fail (current_entry
!= NULL
);
1210 current_entry
->Pause ();
1218 LOG_PLAYLIST ("Playlist::Stop ()\n");
1220 node
= (PlaylistNode
*) entries
->First ();
1221 current_node
= node
; // reset to first node
1222 while (node
!= NULL
) {
1223 node
->GetEntry ()->Stop ();
1224 node
= (PlaylistNode
*) node
->next
;
1229 Playlist::PopulateMediaAttributes ()
1231 PlaylistEntry
*current_entry
= GetCurrentEntry ();
1233 LOG_PLAYLIST ("Playlist::PopulateMediaAttributes ()\n");
1238 current_entry
->PopulateMediaAttributes ();
1242 Playlist::AddEntry (PlaylistEntry
*entry
)
1246 LOG_PLAYLIST ("Playlist::AddEntry (%p) Count: %i\n", entry
, entries
->Length ());
1248 node
= new PlaylistNode (entry
);
1249 entries
->Append (node
);
1252 if (entries
->Length () == 1) {
1253 g_return_if_fail (current_node
== NULL
);
1254 current_node
= node
;
1259 Playlist::ReplaceCurrentEntry (Playlist
*pl
)
1263 PlaylistEntry
*current_entry
= GetCurrentEntry ();
1265 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntry (%p)\n", pl
);
1267 // check for too nested playlist
1269 PlaylistEntry
*e
= this;
1270 while (e
!= NULL
&& e
->IsPlaylist ()) {
1271 IMediaDemuxer
*demuxer
= NULL
;
1273 if (e
->GetMedia () != NULL
)
1274 demuxer
= e
->GetMedia ()->GetDemuxerReffed ();
1276 if (e
->GetObjectType () != Type::PLAYLISTROOT
&& demuxer
!= NULL
&& demuxer
->GetObjectType () == Type::ASXDEMUXER
)
1282 e
= e
->GetParent ();
1285 ErrorEventArgs
*args
= new ErrorEventArgs (MediaError
,
1286 MoonError (MoonError::EXCEPTION
, 4001, "AG_E_NETWORK_ERROR"));
1287 OnEntryFailed (args
);
1293 if (current_entry
->IsPlaylist ()) {
1294 result
= ((Playlist
*) current_entry
)->ReplaceCurrentEntry (pl
);
1296 PlaylistNode
*pln
= new PlaylistNode (pl
);
1297 pl
->MergeWith (current_entry
);
1298 entries
->InsertBefore (pln
, current_node
);
1299 entries
->Remove (current_node
);
1300 pl
->SetParent (this);
1305 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntrY (%p) [DONE]\n", pl
);
1311 Playlist::MergeWith (PlaylistEntry
*entry
)
1313 LOG_PLAYLIST ("Playlist::MergeWith (%p)\n", entry
);
1315 SetBase (entry
->GetBase () ? new Uri (*entry
->GetBase ()) : NULL
);
1316 SetTitle (entry
->GetTitle ());
1317 SetAuthor (entry
->GetAuthor ());
1318 SetAbstract (entry
->GetAbstract ());
1319 SetCopyright (entry
->GetCopyright ());
1321 SetSourceName (entry
->GetSourceName () ? new Uri (*entry
->GetSourceName ()) : NULL
);
1322 if (entry
->HasDuration ())
1323 SetDuration (entry
->GetDuration ());
1324 Initialize (entry
->GetMedia ());
1325 entry
->ClearMedia ();
1329 Playlist::GetCurrentPlaylistEntry ()
1331 PlaylistEntry
*result
= NULL
;
1334 result
= current_node
->GetEntry () ->GetCurrentPlaylistEntry ();
1342 PlaylistRoot::PlaylistRoot (MediaElement
*element
)
1343 : Playlist (Type::PLAYLISTROOT
)
1345 this->element
= element
;
1347 mplayer
= element
->GetMediaPlayer ();
1348 mplayer
->AddHandler (MediaPlayer::MediaEndedEvent
, MediaEndedCallback
, this);
1349 mplayer
->AddHandler (MediaPlayer::BufferUnderflowEvent
, BufferUnderflowCallback
, this);
1354 PlaylistRoot::Dispose ()
1356 if (mplayer
!= NULL
) {
1357 mplayer
->RemoveAllHandlers (this);
1362 Playlist::Dispose ();
1366 PlaylistRoot::IsSingleFile ()
1368 PlaylistEntry
*entry
;
1370 if (GetCount () != 1)
1373 entry
= GetCurrentEntry ();
1377 if (entry
->GetObjectType () == Type::PLAYLISTENTRY
)
1380 return entry
->IsSingleFile ();
1385 PlaylistEntry::DumpInternal (int tabs
)
1387 printf ("%*s%s %i\n", tabs
, "", GetTypeName (), GET_OBJ_ID (this));
1389 printf ("%*sParent: %p %s\n", tabs
, "", parent
, parent
? parent
->GetTypeName () : NULL
);
1390 printf ("%*sFullSourceName: %s\n", tabs
, "", GetFullSourceName ());
1391 printf ("%*sDuration: %s %.2f seconds\n", tabs
, "", HasDuration () ? "yes" : "no", HasDuration () ? GetDuration ()->ToSecondsFloat () : 0.0);
1392 printf ("%*sMedia: %i %s\n", tabs
, "", GET_OBJ_ID (media
), media
? "" : "(null)");
1394 IMediaDemuxer
*demuxer
= media
->GetDemuxerReffed ();
1395 printf ("%*sUri: %s\n", tabs
, "", media
->GetUri ());
1396 printf ("%*sDemuxer: %i %s\n", tabs
, "", GET_OBJ_ID (demuxer
), demuxer
? demuxer
->GetTypeName () : "N/A");
1397 printf ("%*sSource: %i %s\n", tabs
, "", GET_OBJ_ID (media
->GetSource ()), media
->GetSource () ? media
->GetSource ()->GetTypeName () : "N/A");
1405 Playlist::DumpInternal (int tabs
)
1409 PlaylistEntry::DumpInternal (tabs
);
1410 printf ("%*s %i entries:\n", tabs
, "", entries
->Length ());
1411 node
= (PlaylistNode
*) entries
->First ();
1412 while (node
!= NULL
) {
1413 if (node
== current_node
)
1414 printf ("*%*s * CURRENT NODE *\n", tabs
, "");
1415 node
->GetEntry ()->DumpInternal (tabs
+ 2);
1416 node
= (PlaylistNode
*) node
->next
;
1420 PlaylistRoot::Dump ()
1422 printf ("\n\nDUMP OF PLAYLIST\n\n");
1424 printf ("\n\nDUMP OF PLAYLIST DONE\n\n");
1429 PlaylistRoot::SeekCallback (EventObject
*obj
)
1431 PlaylistRoot
*playlist
= (PlaylistRoot
*) obj
;
1434 LOG_PLAYLIST ("PlaylistRoot::SeekCallback ()\n");
1436 if (playlist
->IsDisposed ())
1439 pts_node
= (PtsNode
*) playlist
->seeks
.First ();
1440 if (pts_node
!= NULL
) {
1441 playlist
->seeks
.Unlink (pts_node
);
1442 playlist
->Seek (pts_node
->pts
);
1448 PlaylistRoot::SeekAsync (guint64 pts
)
1450 LOG_PLAYLIST ("PlaylistRoot::SeekAsync (%" G_GUINT64_FORMAT
")\n", pts
);
1451 seeks
.Append (new PtsNode (pts
));
1452 AddTickCall (SeekCallback
);
1456 PlaylistRoot::PlayCallback (EventObject
*obj
)
1458 LOG_PLAYLIST ("Playlist::PlayCallback ()\n");
1460 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1461 if (root
->IsDisposed ())
1467 PlaylistRoot::PlayAsync ()
1469 LOG_PLAYLIST ("Playlist::PlayAsync ()\n");
1470 AddTickCall (PlayCallback
);
1474 PlaylistRoot::PauseCallback (EventObject
*obj
)
1476 LOG_PLAYLIST ("Playlist::PauseCallback ()\n");
1478 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1479 if (root
->IsDisposed ())
1485 PlaylistRoot::PauseAsync ()
1487 LOG_PLAYLIST ("Playlist::PauseAsync ()\n");
1488 AddTickCall (PauseCallback
);
1492 PlaylistRoot::OpenCallback (EventObject
*obj
)
1494 LOG_PLAYLIST ("Playlist::OpenCallback ()\n");
1496 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1497 if (root
->IsDisposed ())
1503 PlaylistRoot::OpenAsync ()
1505 LOG_PLAYLIST ("Playlist::OpenAsync ()\n");
1506 AddTickCall (OpenCallback
);
1510 PlaylistRoot::StopCallback (EventObject
*obj
)
1512 LOG_PLAYLIST ("Playlist::StopCallback ()\n");
1514 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1515 if (root
->IsDisposed ())
1521 PlaylistRoot::StopAsync ()
1523 LOG_PLAYLIST ("Playlist::StopAsync ()\n");
1524 AddTickCall (StopCallback
);
1528 PlaylistRoot::Stop ()
1530 MediaPlayer
*mplayer
;
1532 LOG_PLAYLIST ("PlaylistRoot::Stop ()\n");
1534 mplayer
= GetMediaPlayer ();
1537 if (mplayer
!= NULL
)
1539 // Stop is called async, and if we now emit Open async, we'd possibly not get events in the right order
1540 // example with user code:
1543 // would end up like:
1544 // StopAsync (); -> enqueue Stop
1545 // PlayAsync (); -> enqueue Play
1546 // Stop is called, enqueue Open
1548 Emit (StopEvent
); // we emit the event after enqueuing the Open request, do avoid funky side-effects of event emission.
1552 PlaylistRoot::EmitBufferUnderflowEvent (EventObject
*obj
)
1554 PlaylistRoot
*root
= (PlaylistRoot
*) obj
;
1555 root
->Emit (BufferUnderflowEvent
);
1559 PlaylistRoot::GetMediaPlayer ()
1565 PlaylistRoot::GetCurrentMedia ()
1567 PlaylistEntry
*entry
= GetCurrentEntry ();
1572 return entry
->GetMedia ();
1576 PlaylistRoot::GetElement ()
1582 PlaylistRoot::MediaEndedHandler (MediaPlayer
*mplayer
, EventArgs
*args
)
1584 LOG_PLAYLIST ("PlaylistRoot::MediaEndedHandler (%p, %p)\n", mplayer
, args
);
1588 // Emit (MediaEndedEvent, args);
1592 PlaylistRoot::BufferUnderflowHandler (MediaPlayer
*mplayer
, EventArgs
*args
)
1594 LOG_PLAYLIST ("PlaylistRoot::BufferUnderflowHandler (%p, %p)\n", mplayer
, args
);
1596 if (Surface::InMainThread ()) {
1597 EmitBufferUnderflowEvent (this);
1599 AddTickCall (EmitBufferUnderflowEvent
);
1607 PlaylistParser::PlaylistParser (PlaylistRoot
*root
, IMediaSource
*source
)
1610 this->source
= source
;
1611 this->internal
= NULL
;
1612 this->kind_stack
= NULL
;
1613 this->playlist
= NULL
;
1614 this->current_entry
= NULL
;
1615 this->current_text
= NULL
;
1616 this->error_args
= NULL
;
1620 PlaylistParser::SetSource (IMediaSource
*new_source
)
1624 source
= new_source
;
1630 PlaylistParser::Setup (XmlType type
)
1633 current_entry
= NULL
;
1634 current_text
= NULL
;
1636 was_playlist
= false;
1638 internal
= new PlaylistParserInternal ();
1639 kind_stack
= new List ();
1640 PushCurrentKind (PlaylistKind::Root
);
1642 if (type
== XML_TYPE_ASX3
) {
1643 XML_SetUserData (internal
->parser
, this);
1644 XML_SetElementHandler (internal
->parser
, on_asx_start_element
, on_asx_end_element
);
1645 XML_SetCharacterDataHandler (internal
->parser
, on_asx_text
);
1651 PlaylistParser::Cleanup ()
1654 kind_stack
->Clear (true);
1665 error_args
->unref ();
1670 PlaylistParser::~PlaylistParser ()
1676 str_match (const char *candidate
, const char *tag
)
1678 return g_ascii_strcasecmp (candidate
, tag
) == 0;
1682 PlaylistParser::on_asx_start_element (gpointer user_data
, const char *name
, const char **attrs
)
1684 ((PlaylistParser
*) user_data
)->OnASXStartElement (name
, attrs
);
1688 PlaylistParser::on_asx_end_element (gpointer user_data
, const char *name
)
1690 ((PlaylistParser
*) user_data
)->OnASXEndElement (name
);
1694 PlaylistParser::on_asx_text (gpointer user_data
, const char *data
, int len
)
1696 ((PlaylistParser
*) user_data
)->OnASXText (data
, len
);
1700 is_all_whitespace (const char *str
)
1705 for (int i
= 0; str
[i
] != 0; i
++) {
1720 // To make matters more interesting, the format of the VALUE attribute in the STARTTIME tag isn't
1721 // exactly the same as xaml's or javascript's TimeSpan format.
1723 // The time index, in hours, minutes, seconds, and hundredths of seconds.
1724 // [[hh]:mm]:ss.fract
1726 // The parser seems to stop if it finds a second dot, returnning whatever it had parsed
1729 // At most 4 digits of fract is read, the rest is ignored (even if it's not numbers).
1732 parse_int (const char **pp
, const char *end
, int *result
)
1734 const char *p
= *pp
;
1736 bool success
= false;
1738 while (p
<= end
&& g_ascii_isdigit (*p
)) {
1739 res
= res
* 10 + *p
- '0';
1752 duration_from_asx_str (PlaylistParser
*parser
, const char *str
, Duration
**res
)
1754 const char *end
= str
+ strlen (str
);
1757 int values
[] = {0, 0, 0};
1759 int hh
= 0, mm
= 0, ss
= 0;
1760 int milliseconds
= 0;
1765 if (!g_ascii_isdigit (*p
)) {
1766 parser
->ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1770 for (int i
= 0; i
< 3; i
++) {
1771 if (!parse_int (&p
, end
, &values
[i
])) {
1772 parser
->ParsingError (new ErrorEventArgs (MediaError
,
1773 MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1784 while (digits
>= 0 && g_ascii_isdigit (*p
)) {
1785 milliseconds
+= pow (10.0f
, digits
) * (*p
- '0');
1789 if (counter
== 3 && *p
!= 0 && !g_ascii_isdigit (*p
)) {
1790 parser
->ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1809 parser
->ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 2210, "AG_E_INVALID_ARGUMENT")));
1813 gint64 ms
= ((hh
* 3600) + (mm
* 60) + ss
) * 1000 + milliseconds
;
1814 TimeSpan result
= TimeSpan_FromPts (MilliSeconds_ToPts (ms
));
1815 Duration
*duration
= new Duration (result
);
1823 PlaylistParser::OnASXStartElement (const char *name
, const char **attrs
)
1825 PlaylistKind::Kind kind
= StringToKind (name
);
1829 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), kind = %d\n", name
, attrs
, kind
);
1831 g_free (current_text
);
1832 current_text
= NULL
;
1834 PushCurrentKind (kind
);
1837 case PlaylistKind::Abstract
:
1838 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1839 ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1841 case PlaylistKind::Asx
:
1842 // Here the kind stack should be: Root+Asx
1843 if (kind_stack
->Length () != 2 || !AssertParentKind (PlaylistKind::Root
)) {
1844 ParsingError (new ErrorEventArgs (MediaError
, MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
1848 playlist
= new Playlist (root
, source
);
1850 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1851 if (str_match (attrs
[i
], "VERSION")) {
1852 if (str_match (attrs
[i
+1], "3")) {
1853 playlist_version
= 3;
1854 } else if (str_match (attrs
[i
+1], "3.0")) {
1855 playlist_version
= 3;
1857 ParsingError (new ErrorEventArgs (MediaError
,
1858 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
1860 } else if (str_match (attrs
[i
], "BANNERBAR")) {
1861 ParsingError (new ErrorEventArgs (MediaError
,
1862 MoonError (MoonError::EXCEPTION
, 3007, "Unsupported ASX attribute")));
1863 } else if (str_match (attrs
[i
], "PREVIEWMODE")) {
1864 ParsingError (new ErrorEventArgs (MediaError
,
1865 MoonError (MoonError::EXCEPTION
, 3007, "Unsupported ASX attribute")));
1867 ParsingError (new ErrorEventArgs (MediaError
,
1868 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1872 case PlaylistKind::Author
:
1873 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1874 ParsingError (new ErrorEventArgs (MediaError
,
1875 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1877 case PlaylistKind::Banner
:
1878 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1879 ParsingError (new ErrorEventArgs (MediaError
,
1880 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1882 case PlaylistKind::Base
:
1883 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
1885 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1886 if (str_match (attrs
[i
], "HREF")) {
1887 // TODO: What do we do with this value?
1888 if (GetCurrentContent () != NULL
) {
1891 if (!uri
->Parse (attrs
[i
+1], true)) {
1893 } else if (uri
->GetScheme() == NULL
) {
1895 } else if (uri
->IsScheme ("http") &&
1896 uri
->IsScheme ("https") &&
1897 uri
->IsScheme ("mms") &&
1898 uri
->IsScheme ("rtsp") &&
1899 uri
->IsScheme ("rstpt")) {
1904 GetCurrentContent ()->SetBase (uri
);
1907 ParsingError (new ErrorEventArgs (MediaError
,
1908 MoonError (MoonError::EXCEPTION
, 4001, "AG_E_NETWORK_ERROR")));
1913 ParsingError (new ErrorEventArgs (MediaError
,
1914 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1919 case PlaylistKind::Copyright
:
1920 if (attrs
!= NULL
&& attrs
[0] != NULL
)
1921 ParsingError (new ErrorEventArgs (MediaError
,
1922 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1924 case PlaylistKind::Duration
: {
1926 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1927 if (str_match (attrs
[i
], "VALUE")) {
1928 if (duration_from_asx_str (this, attrs
[i
+1], &dur
)) {
1929 if (GetCurrentEntry () != NULL
&& GetParentKind () != PlaylistKind::Ref
) {
1930 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name
, attrs
, TimeSpan_ToSecondsFloat (dur
->GetTimeSpan()));
1931 GetCurrentEntry ()->SetDuration (dur
);
1935 ParsingError (new ErrorEventArgs (MediaError
,
1936 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1942 case PlaylistKind::Entry
: {
1943 bool client_skip
= true;
1944 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1945 if (str_match (attrs
[i
], "CLIENTSKIP")) {
1946 // TODO: What do we do with this value?
1947 if (str_match (attrs
[i
+1], "YES")) {
1949 } else if (str_match (attrs
[i
+1], "NO")) {
1950 client_skip
= false;
1952 ParsingError (new ErrorEventArgs (MediaError
,
1953 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
1956 } else if (str_match (attrs
[i
], "SKIPIFREF")) {
1957 ParsingError (new ErrorEventArgs (MediaError
,
1958 MoonError (MoonError::EXCEPTION
, 3007, "Unsupported ASX attribute")));
1961 ParsingError (new ErrorEventArgs (MediaError
,
1962 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1966 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
1967 entry
->SetClientSkip (client_skip
);
1968 playlist
->AddEntry (entry
);
1969 current_entry
= entry
;
1972 case PlaylistKind::EntryRef
: {
1974 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
1975 if (str_match (attrs
[i
], "HREF")) {
1977 href
= g_strdup (attrs
[i
+1]);
1978 // Docs says this attribute isn't unsupported, but an error is emitted.
1979 //} else if (str_match (attrs [i], "CLIENTBIND")) {
1980 // // TODO: What do we do with this value?
1982 ParsingError (new ErrorEventArgs (MediaError
,
1983 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
1990 if (!uri
->Parse (href
)) {
1993 ParsingError (new ErrorEventArgs (MediaError
,
1994 MoonError (MoonError::EXCEPTION
, 1001, "AG_E_UNKNOWN_ERROR")));
1998 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
2000 entry
->SetSourceName (uri
);
2002 playlist
->AddEntry (entry
);
2003 current_entry
= entry
;
2006 case PlaylistKind::LogUrl
:
2007 if (attrs
!= NULL
&& attrs
[0] != NULL
)
2008 ParsingError (new ErrorEventArgs (MediaError
,
2009 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2011 case PlaylistKind::MoreInfo
:
2012 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
2013 if (str_match (attrs
[i
], "HREF")) {
2014 if (GetCurrentEntry () != NULL
)
2015 GetCurrentEntry ()->SetInfoURL (attrs
[i
+1]);
2016 } else if (str_match (attrs
[i
], "TARGET")) {
2017 if (GetCurrentEntry () != NULL
)
2018 GetCurrentEntry ()->SetInfoTarget (attrs
[i
+1]);
2020 ParsingError (new ErrorEventArgs (MediaError
,
2021 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2026 case PlaylistKind::StartTime
: {
2028 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
2029 if (str_match (attrs
[i
], "VALUE")) {
2030 if (duration_from_asx_str (this, attrs
[i
+1], &dur
) && dur
->HasTimeSpan ()) {
2031 if (GetCurrentEntry () != NULL
&& GetParentKind () != PlaylistKind::Ref
) {
2032 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name
, attrs
, TimeSpan_ToSecondsFloat (dur
->GetTimeSpan()));
2033 GetCurrentEntry ()->SetStartTime (dur
->GetTimeSpan ());
2037 ParsingError (new ErrorEventArgs (MediaError
,
2038 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2045 case PlaylistKind::Ref
: {
2046 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
2047 if (str_match (attrs
[i
], "HREF")) {
2048 if (GetCurrentEntry () != NULL
&& GetCurrentEntry ()->GetSourceName () == NULL
) {
2050 if (uri
->Parse (attrs
[i
+1])) {
2051 GetCurrentEntry ()->SetSourceName (uri
);
2054 ParsingError (new ErrorEventArgs (MediaError
,
2055 MoonError (MoonError::EXCEPTION
, 1001, "AG_E_UNKNOWN_ERROR")));
2060 ParsingError (new ErrorEventArgs (MediaError
,
2061 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2067 case PlaylistKind::Param
: {
2068 const char *name
= NULL
;
2069 const char *value
= NULL
;
2071 for (int i
= 0; attrs
[i
] != NULL
; i
+= 2) {
2072 if (str_match (attrs
[i
], "name")) {
2073 name
= attrs
[i
+ 1];
2074 } else if (str_match (attrs
[i
], "value")) {
2075 value
= attrs
[i
+ 1];
2077 ParsingError (new ErrorEventArgs (MediaError
,
2078 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2082 if (value
!= NULL
&& value
[0] != 0 && name
!= NULL
&& name
[0] != 0) {
2083 PlaylistEntry
*entry
= GetCurrentEntry ();
2088 entry
->AddParams (name
, value
);
2094 case PlaylistKind::Title
:
2095 if (attrs
!= NULL
&& attrs
[0] != NULL
)
2096 ParsingError (new ErrorEventArgs (MediaError
,
2097 MoonError (MoonError::EXCEPTION
, 3005, "Invalid ASX attribute")));
2099 case PlaylistKind::StartMarker
:
2100 case PlaylistKind::EndMarker
:
2101 case PlaylistKind::Repeat
:
2102 case PlaylistKind::Event
:
2103 ParsingError (new ErrorEventArgs (MediaError
,
2104 MoonError (MoonError::EXCEPTION
, 3006, "Unsupported ASX element")));
2106 case PlaylistKind::Root
:
2107 case PlaylistKind::Unknown
:
2109 LOG_PLAYLIST ("PlaylistParser::OnStartElement ('%s', %p): Unknown kind: %d\n", name
, attrs
, kind
);
2110 ParsingError (new ErrorEventArgs (MediaError
,
2111 MoonError (MoonError::EXCEPTION
, 3004, "Invalid ASX element")));
2117 PlaylistParser::OnASXEndElement (const char *name
)
2119 PlaylistKind::Kind kind
= GetCurrentKind ();
2122 LOG_PLAYLIST ("PlaylistParser::OnEndElement (%s), GetCurrentKind (): %d, GetCurrentKind () to string: %s\n", name
, kind
, KindToString (kind
));
2125 case PlaylistKind::Abstract
:
2126 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2128 if (GetCurrentContent () != NULL
)
2129 GetCurrentContent ()->SetAbstract (current_text
);
2131 case PlaylistKind::Author
:
2132 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2134 if (GetCurrentContent () != NULL
)
2135 GetCurrentContent ()->SetAuthor (current_text
);
2137 case PlaylistKind::Base
:
2138 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2141 case PlaylistKind::Copyright
:
2142 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2144 if (GetCurrentContent () != NULL
)
2145 GetCurrentContent ()->SetCopyright (current_text
);
2147 case PlaylistKind::Duration
:
2148 if (!AssertParentKind (PlaylistKind::Entry
| PlaylistKind::Ref
))
2150 if (current_text
== NULL
)
2152 duration_from_asx_str (this, current_text
, &dur
);
2153 if (GetCurrentEntry () != NULL
)
2154 GetCurrentEntry ()->SetDuration (dur
);
2156 case PlaylistKind::Entry
:
2157 if (!AssertParentKind (PlaylistKind::Asx
))
2159 if (!is_all_whitespace (current_text
)) {
2160 ParsingError (new ErrorEventArgs (MediaError
,
2161 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2164 case PlaylistKind::EntryRef
:
2165 if (!AssertParentKind (PlaylistKind::Asx
))
2168 case PlaylistKind::StartTime
:
2169 if (!AssertParentKind (PlaylistKind::Entry
| PlaylistKind::Ref
))
2171 if (!is_all_whitespace (current_text
)) {
2172 ParsingError (new ErrorEventArgs (MediaError
,
2173 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2176 case PlaylistKind::Title
:
2177 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2179 if (GetCurrentContent () != NULL
)
2180 GetCurrentContent ()->SetTitle (current_text
);
2182 case PlaylistKind::Asx
:
2183 if (playlist_version
== 3)
2184 was_playlist
= true;
2185 if (!AssertParentKind (PlaylistKind::Root
))
2188 case PlaylistKind::Ref
:
2189 if (!AssertParentKind (PlaylistKind::Entry
))
2191 if (!is_all_whitespace (current_text
)) {
2192 ParsingError (new ErrorEventArgs (MediaError
,
2193 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2196 case PlaylistKind::MoreInfo
:
2197 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2199 if (!is_all_whitespace (current_text
)) {
2200 ParsingError (new ErrorEventArgs (MediaError
,
2201 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2204 case PlaylistKind::Param
:
2205 if (!AssertParentKind (PlaylistKind::Asx
| PlaylistKind::Entry
))
2207 if (!is_all_whitespace (current_text
)) {
2208 ParsingError (new ErrorEventArgs (MediaError
,
2209 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2213 LOG_PLAYLIST ("PlaylistParser::OnEndElement ('%s'): Unknown kind %d.\n", name
, kind
);
2214 ParsingError (new ErrorEventArgs (MediaError
,
2215 MoonError (MoonError::EXCEPTION
, 3004, "Invalid ASX element")));
2219 if (current_text
!= NULL
) {
2220 g_free (current_text
);
2221 current_text
= NULL
;
2224 switch (GetCurrentKind ()) {
2225 case PlaylistKind::Entry
:
2235 PlaylistParser::OnASXText (const char *text
, int len
)
2237 char *a
= g_strndup (text
, len
);
2240 char *p
= g_strndup (text
, len
);
2241 for (int i
= 0; p
[i
] != 0; i
++)
2242 if (p
[i
] == 10 || p
[i
] == 13)
2245 LOG_PLAYLIST ("PlaylistParser::OnText (%s, %d)\n", p
, len
);
2249 if (current_text
== NULL
) {
2252 char *b
= g_strconcat (current_text
, a
, NULL
);
2253 g_free (current_text
);
2259 PlaylistParser::Is (IMediaSource
*source
, const char *asx_header
)
2261 bool result
= false;
2262 int asx_header_length
= strlen (asx_header
);
2263 unsigned char buffer
[20];
2266 result
= source
->Peek ((guint8
*) buffer
, asx_header_length
);
2270 // skip any whitespace
2271 unsigned char c
= buffer
[0];
2277 result
= source
->ReadAll ((guint8
*) buffer
, 1);
2283 if (buffer
[1] == 0xbb && buffer
[2] == 0xbf) { // UTF-8 BOM: EF BB BF
2284 result
= source
->ReadAll ((guint8
*) buffer
, 3);
2289 // TODO: there might be other BOMs we should handle too
2293 result
= !g_ascii_strncasecmp ((const char *) buffer
, asx_header
, asx_header_length
);
2300 source
->Seek (0, SEEK_SET
);
2306 PlaylistParser::IsASX3 (IMediaSource
*source
)
2308 return Is (source
, "<ASX");
2312 PlaylistParser::IsASX2 (IMediaSource
*source
)
2314 return Is (source
, "[Reference]");
2318 PlaylistParser::ParseASX2 ()
2320 const int BUFFER_SIZE
= 1024;
2322 char buffer
[BUFFER_SIZE
];
2328 playlist_version
= 2;
2330 bytes_read
= source
->ReadSome (buffer
, BUFFER_SIZE
);
2331 if (bytes_read
< 0) {
2332 LOG_PLAYLIST_WARN ("Could not read asx document for parsing.\n");
2336 key_file
= g_key_file_new ();
2337 if (!g_key_file_load_from_data (key_file
, buffer
, bytes_read
,
2338 G_KEY_FILE_NONE
, NULL
)) {
2339 LOG_PLAYLIST_WARN ("Invalid asx2 document.\n");
2340 g_key_file_free (key_file
);
2344 ref
= g_key_file_get_value (key_file
, "Reference", "Ref1", NULL
);
2346 LOG_PLAYLIST_WARN ("Could not find Ref1 entry in asx2 document.\n");
2347 g_key_file_free (key_file
);
2351 if (!g_str_has_prefix (ref
, "http://")) {
2352 LOG_PLAYLIST_WARN ("Could not find a valid uri within Ref1 entry in asx2 document.\n");
2354 g_key_file_free (key_file
);
2358 mms_uri
= g_strdup_printf ("mms://%s", strstr (ref
, "http://") + strlen ("http://"));
2360 g_key_file_free (key_file
);
2363 playlist
= new Playlist (root
, source
);
2365 PlaylistEntry
*entry
= new PlaylistEntry (playlist
);
2367 if (uri
->Parse (mms_uri
)) {
2368 entry
->SetSourceName (uri
);
2372 playlist
->AddEntry (entry
);
2373 current_entry
= entry
;
2379 PlaylistParser::TryFixError (gint8
*current_buffer
, int bytes_read
, int total_bytes_read
)
2383 if (XML_GetErrorCode (internal
->parser
) != XML_ERROR_INVALID_TOKEN
)
2386 int index
= XML_GetErrorByteIndex (internal
->parser
);
2388 // check that the index is within the buffer
2389 if (index
> total_bytes_read
|| index
< total_bytes_read
- bytes_read
)
2392 // index is from the first character parsed, we need to subtract the
2393 // read bytes from previous buffers.
2394 index
-= (total_bytes_read
- bytes_read
);
2396 LOG_PLAYLIST ("Attempting to fix invalid token error index: %d\n", index
);
2398 // OK, so we are going to guess that we are in an attribute here and walk back
2399 // until we hit a control char that should be escaped.
2400 char * escape
= NULL
;
2401 while (index
>= 0) {
2402 switch (current_buffer
[index
]) {
2404 escape
= g_strdup ("&");
2407 escape
= g_strdup ("<");
2410 escape
= g_strdup (">");
2421 LOG_PLAYLIST_WARN ("Unable to find an invalid escape character to fix in ASX: %s.\n", current_buffer
);
2426 int escape_len
= strlen (escape
);
2427 int new_size
= source
->GetSize () + escape_len
- 1;
2428 int patched_size
= internal
->bytes_read
+ bytes_read
+ escape_len
- 1;
2429 gint8
* new_buffer
= (gint8
*) g_malloc (new_size
);
2431 source
->Seek (0, SEEK_SET
);
2432 source
->ReadSome (new_buffer
, internal
->bytes_read
);
2434 memcpy (new_buffer
+ internal
->bytes_read
, current_buffer
, index
);
2435 memcpy (new_buffer
+ internal
->bytes_read
+ index
, escape
, escape_len
);
2436 memcpy (new_buffer
+ internal
->bytes_read
+ index
+ escape_len
, current_buffer
+ index
+ 1, bytes_read
- index
- 1);
2438 source
->Seek (internal
->bytes_read
+ bytes_read
, SEEK_SET
);
2439 source
->ReadSome (new_buffer
+ patched_size
, new_size
- patched_size
);
2441 media
= source
->GetMediaReffed ();
2443 MemorySource
*reparse_source
= new MemorySource (media
, new_buffer
, new_size
);
2444 SetSource (reparse_source
);
2445 reparse_source
->unref ();
2447 internal
->reparse
= true;
2450 // Clear out errors in the old buffer
2451 error_args
->unref ();
2465 PlaylistParser::Parse ()
2468 gint64 last_available_pos
;
2471 LOG_PLAYLIST ("PlaylistParser::Parse ()\n");
2474 // Don't try to parse anything until we have all the data.
2475 if (internal
!= NULL
)
2476 internal
->reparse
= false;
2477 size
= source
->GetSize ();
2478 last_available_pos
= source
->GetLastAvailablePosition ();
2479 if (size
!= -1 && last_available_pos
!= -1 && size
!= last_available_pos
)
2480 return MEDIA_NOT_ENOUGH_DATA
;
2482 if (this->IsASX2 (source
)) {
2483 /* Parse as a asx2 mms file */
2484 Setup (XML_TYPE_NONE
);
2485 result
= this->ParseASX2 ();
2486 } else if (this->IsASX3 (source
)) {
2487 Setup (XML_TYPE_ASX3
);
2488 result
= this->ParseASX3 ();
2492 } while (result
&& internal
->reparse
);
2494 return result
? MEDIA_SUCCESS
: MEDIA_FAIL
;
2498 PlaylistParser::ParseASX3 ()
2501 int total_bytes_read
= 0;
2504 // asx documents don't tend to be very big, so there's no need for a big buffer
2505 const int BUFFER_SIZE
= 1024;
2508 buffer
= XML_GetBuffer(internal
->parser
, BUFFER_SIZE
);
2509 if (buffer
== NULL
) {
2510 fprintf (stderr
, "Could not allocate memory for asx document parsing.\n");
2514 bytes_read
= source
->ReadSome (buffer
, BUFFER_SIZE
);
2515 if (bytes_read
< 0) {
2516 fprintf (stderr
, "Could not read asx document for parsing.\n");
2520 total_bytes_read
+= bytes_read
;
2521 if (!XML_ParseBuffer (internal
->parser
, bytes_read
, bytes_read
== 0)) {
2522 if (error_args
!= NULL
)
2525 switch (XML_GetErrorCode (internal
->parser
)) {
2526 case XML_ERROR_NO_ELEMENTS
:
2527 ParsingError (new ErrorEventArgs (MediaError
,
2528 MoonError (MoonError::EXCEPTION
, 7000, "unexpected end of input")));
2530 case XML_ERROR_DUPLICATE_ATTRIBUTE
:
2531 ParsingError (new ErrorEventArgs (MediaError
,
2532 MoonError (MoonError::EXCEPTION
, 7031, "wfc: unique attribute spec")));
2534 case XML_ERROR_INVALID_TOKEN
:
2535 // save error args in case the error fixing fails (in which case we want this error, not the error the error fixing caused)
2536 error_args
= new ErrorEventArgs (MediaError
,
2537 MoonError (MoonError::EXCEPTION
, 7007, "quote expected"));
2538 if (TryFixError ((gint8
*) buffer
, bytes_read
, total_bytes_read
))
2542 char *msg
= g_strdup_printf ("%s %d (%d, %d)",
2543 XML_ErrorString (XML_GetErrorCode (internal
->parser
)), (int) XML_GetErrorCode (internal
->parser
),
2544 (int) XML_GetCurrentLineNumber (internal
->parser
), (int) XML_GetCurrentColumnNumber (internal
->parser
));
2545 ParsingError (new ErrorEventArgs (MediaError
,
2546 MoonError (MoonError::EXCEPTION
, 3000, msg
)));
2552 if (bytes_read
== 0)
2555 internal
->bytes_read
+= bytes_read
;
2558 return playlist
!= NULL
;
2562 PlaylistParser::GetCurrentContent ()
2564 if (current_entry
!= NULL
)
2565 return current_entry
;
2571 PlaylistParser::GetCurrentEntry ()
2573 return current_entry
;
2577 PlaylistParser::EndEntry ()
2579 this->current_entry
= NULL
;
2583 PlaylistParser::PushCurrentKind (PlaylistKind::Kind kind
)
2585 kind_stack
->Append (new KindNode (kind
));
2586 LOG_PLAYLIST ("PlaylistParser::Push (%d)\n", kind
);
2590 PlaylistParser::PopCurrentKind ()
2592 LOG_PLAYLIST ("PlaylistParser::PopCurrentKind (), current: %d\n", ((KindNode
*)kind_stack
->Last ())->kind
);
2593 kind_stack
->Remove (kind_stack
->Last ());
2597 PlaylistParser::GetCurrentKind ()
2599 KindNode
*node
= (KindNode
*) kind_stack
->Last ();
2604 PlaylistParser::GetParentKind ()
2606 KindNode
*node
= (KindNode
*) kind_stack
->Last ()->prev
;
2611 PlaylistParser::AssertParentKind (int kind
)
2613 LOG_PLAYLIST ("PlaylistParser::AssertParentKind (%d), GetParentKind: %d, result: %d\n", kind
, GetParentKind (), GetParentKind () & kind
);
2615 if (GetParentKind () & kind
)
2618 ParsingError (new ErrorEventArgs (MediaError
,
2619 MoonError (MoonError::EXCEPTION
, 3008, "ASX parse error")));
2625 PlaylistParser::ParsingError (ErrorEventArgs
*args
)
2627 LOG_PLAYLIST ("PlaylistParser::ParsingError (%s)\n", args
->GetErrorMessage());
2629 XML_StopParser (internal
->parser
, false);
2633 return; // don't overwrite any previous errors.
2635 error_args
= args
; // don't ref, this method is called like this: ParsingError (new ErrorEventArgs (...));, so the caller gives us the ref he has
2639 PlaylistKind
PlaylistParser::playlist_kinds
[] = {
2641 PlaylistKind ("ABSTRACT", PlaylistKind::Abstract
),
2642 PlaylistKind ("ASX", PlaylistKind::Asx
),
2643 PlaylistKind ("ROOT", PlaylistKind::Root
),
2644 PlaylistKind ("AUTHOR", PlaylistKind::Author
),
2645 PlaylistKind ("BANNER", PlaylistKind::Banner
),
2646 PlaylistKind ("BASE", PlaylistKind::Base
),
2647 PlaylistKind ("COPYRIGHT", PlaylistKind::Copyright
),
2648 PlaylistKind ("DURATION", PlaylistKind::Duration
),
2649 PlaylistKind ("ENTRY", PlaylistKind::Entry
),
2650 PlaylistKind ("ENTRYREF", PlaylistKind::EntryRef
),
2651 PlaylistKind ("LOGURL", PlaylistKind::LogUrl
),
2652 PlaylistKind ("MOREINFO", PlaylistKind::MoreInfo
),
2653 PlaylistKind ("REF", PlaylistKind::Ref
),
2654 PlaylistKind ("STARTTIME", PlaylistKind::StartTime
),
2655 PlaylistKind ("TITLE", PlaylistKind::Title
),
2656 PlaylistKind ("STARTMARKER", PlaylistKind::StartMarker
),
2657 PlaylistKind ("REPEAT", PlaylistKind::Repeat
),
2658 PlaylistKind ("ENDMARKER", PlaylistKind::EndMarker
),
2659 PlaylistKind ("PARAM", PlaylistKind::Param
),
2660 PlaylistKind ("EVENT", PlaylistKind::Event
),
2662 PlaylistKind (NULL
, PlaylistKind::Unknown
)
2666 PlaylistParser::StringToKind (const char *str
)
2668 PlaylistKind::Kind kind
= PlaylistKind::Unknown
;
2670 for (int i
= 0; playlist_kinds
[i
].str
!= NULL
; i
++) {
2671 if (str_match (str
, playlist_kinds
[i
].str
)) {
2672 kind
= playlist_kinds
[i
].kind
;
2677 LOG_PLAYLIST ("PlaylistParser::StringToKind ('%s') = %d\n", str
, kind
);
2683 PlaylistParser::KindToString (PlaylistKind::Kind kind
)
2685 const char *result
= NULL
;
2687 for (int i
= 0; playlist_kinds
[i
].str
!= NULL
; i
++) {
2688 if (playlist_kinds
[i
].kind
== kind
) {
2689 result
= playlist_kinds
[i
].str
;
2694 LOG_PLAYLIST ("PlaylistParser::KindToString (%d) = '%s'\n", kind
, result
);