2009-12-03 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / playlist.cpp
blob1b748b6f4b1139734b08de16266e800d79bb9d94
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * playlist.cpp:
5 * Contact:
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.
13 #include <config.h>
14 #include <expat.h>
15 #include <string.h>
16 #include <math.h>
17 #include <glib.h>
19 #include "playlist.h"
20 #include "clock.h"
21 #include "mediaelement.h"
22 #include "debug.h"
23 #include "media.h"
24 #include "mediaplayer.h"
27 * PlaylistParserInternal
30 PlaylistParserInternal::PlaylistParserInternal ()
32 parser = XML_ParserCreate (NULL);
33 bytes_read = 0;
34 reparse = false;
37 PlaylistParserInternal::~PlaylistParserInternal ()
39 XML_ParserFree (parser);
40 parser = NULL;
44 * PlaylistNode
47 PlaylistNode::PlaylistNode (PlaylistEntry *entry) : List::Node ()
49 if (entry)
50 entry->ref ();
51 this->entry = entry;
54 PlaylistNode::~PlaylistNode ()
56 if (entry) {
57 entry->unref ();
58 entry = NULL;
63 * PlaylistEntry
66 PlaylistEntry::PlaylistEntry (Playlist *parent)
67 : EventObject (Type::PLAYLISTENTRY, false)
69 LOG_PLAYLIST ("PlaylistEntry::PlaylistEntry (%p)\n", parent);
71 Init (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);
80 Init (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");
90 Init (NULL);
93 void
94 PlaylistEntry::Dispose ()
96 LOG_PLAYLIST ("PlaylistEntry::Dispose () id: %i media: %i\n", GET_OBJ_ID (this), GET_OBJ_ID (media));
98 if (media) {
99 Media *tmp = media;
100 media = NULL;
101 tmp->RemoveSafeHandlers (this);
102 tmp->DisposeObject (tmp);
103 tmp->unref ();
106 delete source_name;
107 source_name = NULL;
108 g_free (full_source_name);
109 full_source_name = NULL;
111 delete base;
112 base = NULL;
113 g_free (title);
114 title = NULL;
115 g_free (author);
116 author = NULL;
117 g_free (abstract);
118 abstract = NULL;
119 g_free (copyright);
120 copyright = NULL;
121 g_free (info_target);
122 info_target = NULL;
123 g_free (info_url);
124 info_url = NULL;
126 parent = NULL;
127 if (params != NULL) {
128 g_hash_table_destroy (params);
129 params = NULL;
132 EventObject::Dispose ();
135 void
136 PlaylistEntry::Init (Playlist *parent)
138 // Parent might be null
139 this->parent = parent;
140 this->media = NULL;
141 source_name = NULL;
142 full_source_name = NULL;
143 start_time = 0;
144 duration = NULL;
145 play_when_available = false;
146 base = NULL;
147 title = NULL;
148 author = NULL;
149 abstract = NULL;
150 copyright = NULL;
151 info_target = NULL;
152 info_url = NULL;
153 client_skip = true;
154 is_live = false;
155 set_values = (PlaylistKind::Kind) 0;
156 opened = false;
157 params = NULL;
160 void
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);
175 this->media = media;
176 this->media->ref ();
179 void
180 PlaylistEntry::InitializeWithStream (ManagedStreamCallbacks *callbacks)
182 Media *media;
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);
190 Initialize (media);
192 source = new ManagedStreamSource (media, callbacks);
193 media->Initialize (source);
194 if (!media->HasReportedError ())
195 media->OpenAsync ();
196 media->unref ();
197 source->unref ();
200 void
201 PlaylistEntry::InitializeWithSource (IMediaSource *source)
203 Media *media;
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);
213 Initialize (media);
215 media->Initialize (source);
216 if (!media->HasReportedError ())
217 media->OpenAsync ();
218 media->unref ();
221 void
222 PlaylistEntry::InitializeWithUri (const char *uri)
224 Media *media;
225 PlaylistRoot *root = GetRoot ();
227 g_return_if_fail (uri != NULL);
228 g_return_if_fail (root != NULL);
230 media = new Media (root);
231 Initialize (media);
232 media->Initialize (uri);
233 if (!media->HasReportedError ())
234 media->OpenAsync ();
235 media->unref ();
238 void
239 PlaylistEntry::InitializeWithDownloader (Downloader *dl, const char *PartName)
241 Media *media;
242 PlaylistRoot *root = GetRoot ();
244 g_return_if_fail (dl != NULL);
245 g_return_if_fail (root != NULL);
247 media = new Media (root);
248 Initialize (media);
249 media->Initialize (dl, PartName);
250 if (!media->HasReportedError ())
251 media->OpenAsync ();
252 media->unref ();
255 void
256 PlaylistEntry::InitializeWithDemuxer (IMediaDemuxer *demuxer)
258 Media *media;
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);
268 Initialize (media);
269 media->Initialize (demuxer);
270 if (!media->HasReportedError ())
271 media->OpenAsync ();
272 media->unref ();
275 void
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);
287 void
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);
304 void
305 PlaylistEntry::OpenCompletedHandler (Media *media, EventArgs *args)
307 PlaylistRoot *root = GetRoot ();
308 IMediaDemuxer *demuxer = NULL;
309 Playlist *playlist;
311 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p)\n", media, args);
312 opened = true;
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) {
328 goto cleanup;
331 parent->ReplaceCurrentEntry (playlist);
332 playlist->Open ();
333 } else {
334 if (parent->GetCurrentEntry () == this) {
335 OpenMediaPlayer ();
336 } else {
337 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p): opened entry in advance, waiting for current entry to finish.\n", media, args);
341 cleanup:
342 if (demuxer)
343 demuxer->unref ();
346 void
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);
355 if (args)
356 args->ref ();
357 root->Emit (PlaylistRoot::SeekingEvent, args);
360 void
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);
369 if (args)
370 args->ref ();
371 root->Emit (PlaylistRoot::SeekCompletedEvent, args);
374 void
375 PlaylistEntry::CurrentStateChangedHandler (Media *media, EventArgs *args)
377 LOG_PLAYLIST ("PlaylistEntry::CurrentStateChangedHandler (%p, %p)\n", media, args);
380 void
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);
390 void
391 PlaylistEntry::DownloadProgressChangedHandler (Media *media, EventArgs *args)
393 PlaylistRoot *root;
395 LOG_PLAYLIST ("PlaylistEntry::DownloadProgressChanged (%p, %p %.2f). Disposed: %i\n", media, args, args ? ((ProgressEventArgs *) args)->progress : -1.0, IsDisposed ());
397 if (IsDisposed ())
398 return;
400 root = GetRoot ();
402 g_return_if_fail (root != NULL);
404 if (args)
405 args->ref ();
406 root->Emit (PlaylistRoot::DownloadProgressChangedEvent, args);
409 void
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);
416 if (root == NULL)
417 return; // this might happen if the media is still buffering and we're in the process of getting cleaned up
419 if (args)
420 args->ref ();
421 root->Emit (PlaylistRoot::BufferingProgressChangedEvent, args);
424 void
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);
434 void
435 PlaylistEntry::AddParams (const char *name, const char *value)
437 char *uppername = g_ascii_strup (name, strlen (name));
438 if (!strcmp (uppername, "AUTHOR")) {
439 SetAuthor (value);
440 } else if (!strcmp (uppername, "ABSTRACT")) {
441 SetAbstract (value);
442 } else if (!strcmp (uppername, "TITLE")) {
443 SetTitle (value);
444 } else if (!strcmp (uppername, "COPYRIGHT")) {
445 SetCopyright (value);
446 } else if (!strcmp (uppername, "INFOTARGET")) {
447 SetInfoTarget (value);
448 } else if (!strcmp (uppername, "INFOURL")) {
449 SetInfoURL (value);
450 } else {
451 if (params == NULL)
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));
456 uppername = NULL;
459 g_free (uppername);
462 Uri *
463 PlaylistEntry::GetBase ()
465 return base;
468 Uri *
469 PlaylistEntry::GetBaseInherited ()
471 if (base != NULL)
472 return base;
473 if (parent != NULL)
474 return parent->GetBaseInherited ();
475 return NULL;
478 void
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)) {
484 this->base = base;
485 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Base);
486 } else {
487 delete base;
491 const char *
492 PlaylistEntry::GetTitle ()
494 return title;
497 void
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);
506 const char *
507 PlaylistEntry::GetAuthor ()
509 return author;
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);
520 const char *
521 PlaylistEntry::GetAbstract ()
523 return abstract;
526 void
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);
535 const char *
536 PlaylistEntry::GetCopyright ()
538 return copyright;
541 void
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);
550 Uri *
551 PlaylistEntry::GetSourceName ()
553 return source_name;
556 void
557 PlaylistEntry::SetSourceName (Uri *source_name)
559 if (this->source_name)
560 delete this->source_name;
561 this->source_name = source_name;
564 TimeSpan
565 PlaylistEntry::GetStartTime ()
567 return start_time;
570 void
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);
579 Duration*
580 PlaylistEntry::GetDuration ()
582 return duration;
585 Duration *
586 PlaylistEntry::GetInheritedDuration ()
588 if (HasDuration ()) {
589 return GetDuration ();
590 } else if (parent != NULL) {
591 return parent->GetInheritedDuration ();
592 } else {
593 return NULL;
597 bool
598 PlaylistEntry::HasInheritedDuration ()
600 if (HasDuration ()) {
601 return true;
602 } else if (parent != NULL) {
603 return parent->HasInheritedDuration ();
604 } else {
605 return false;
609 void
610 PlaylistEntry::SetDuration (Duration *duration)
612 if (!(set_values & PlaylistKind::Duration)) {
613 this->duration = duration;
614 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Duration);
618 const char *
619 PlaylistEntry::GetInfoTarget ()
621 return info_target;
624 void
625 PlaylistEntry::SetInfoTarget (const char *info_target)
627 g_free (this->info_target);
628 this->info_target = g_strdup (info_target);
631 const char *
632 PlaylistEntry::GetInfoURL ()
634 return info_url;
637 void
638 PlaylistEntry::SetInfoURL (const char *info_url)
640 g_free (this->info_url);
641 this->info_url = g_strdup (info_url);
644 bool
645 PlaylistEntry::GetClientSkip ()
647 return client_skip;
650 void
651 PlaylistEntry::SetClientSkip (bool value)
653 client_skip = value;
656 MediaElement *
657 PlaylistEntry::GetElement ()
659 g_return_val_if_fail (parent != NULL, NULL);
661 return parent->GetElement ();
664 void
665 PlaylistEntry::ClearMedia ()
667 g_return_if_fail (media != NULL);
668 media->unref ();
669 media = NULL;
672 MediaPlayer *
673 PlaylistEntry::GetMediaPlayer ()
675 PlaylistRoot *root = GetRoot ();
677 g_return_val_if_fail (root != NULL, NULL);
679 return root->GetMediaPlayer ();
682 static void
683 add_attribute (MediaAttributeCollection *attributes, const char *name, const char *attr)
685 if (!attr)
686 return;
688 MediaAttribute *attribute = new MediaAttribute ();
689 attribute->SetValue (attr);
690 attribute->SetName (name);
692 attributes->Add (attribute);
693 attribute->unref ();
696 static void
697 add_attribute_glib (const char *name, const char *value, MediaAttributeCollection *attributes)
699 add_attribute (attributes, name, value);
702 void
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);
724 } else {
725 attributes->Clear ();
728 while (current != NULL) {
729 if (abstract == NULL)
730 abstract = current->GetAbstract ();
731 if (author == NULL)
732 author = current->GetAuthor ();
733 if (copyright == NULL)
734 copyright = current->GetCopyright ();
735 if (title == NULL)
736 title = current->GetTitle ();
737 if (infotarget == NULL)
738 infotarget = current->GetInfoTarget ();
739 if (infourl == NULL)
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);
755 current = this;
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 ();
763 const char *
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
775 * 03 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 ();
794 Uri *result = NULL;
795 const char *pathsep;
796 char *base_path;
798 //printf ("PlaylistEntry::GetFullSourceName (), base: %s, current: %s\n", base ? base->ToString () : "NULL", current ? current->ToString () : "NULL");
800 if (current == NULL) {
801 return NULL;
802 } else if (current->GetHost () != NULL) {
803 //printf (" current host (%s) is something, scheme: %s\n", current->GetHost (), current->scheme);
804 result = current;
805 } else if (base != NULL) {
806 result = new Uri ();
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());
819 } else {
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);
825 } else {
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);
829 g_free (base_path);
831 } else {
832 //printf (" base path (%s) does not contain a /\n", base->path);
833 result->path = g_strjoin (NULL, base->GetPath(), "/", current->GetPath(), NULL);
836 } else {
837 //printf (" there's no base\n");
838 result = current;
841 full_source_name = result->ToString ();
843 //printf (" result: %s\n", full_source_name);
845 if (result != base && result != current)
846 delete result;
848 return full_source_name;
851 void
852 PlaylistEntry::Open ()
854 LOG_PLAYLIST ("PlaylistEntry::Open (), media = %p, FullSourceName = %s\n", media, GetFullSourceName ());
856 if (!media) {
857 g_return_if_fail (GetFullSourceName () != NULL);
858 InitializeWithUri (GetFullSourceName ());
859 } else if (opened) {
860 OpenMediaPlayer ();
861 } else {
862 media->OpenAsync ();
866 void
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);
878 media->PlayAsync ();
879 mplayer->Play ();
881 root->Emit (PlaylistRoot::PlayEvent);
884 void
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 ();
898 mplayer->Pause ();
900 root->Emit (PlaylistRoot::PauseEvent);
903 void
904 PlaylistEntry::Stop ()
906 LOG_PLAYLIST ("PlaylistEntry::Stop ()\n");
908 play_when_available = false;
909 if (media != NULL)
910 media->StopAsync ();
913 Media *
914 PlaylistEntry::GetMedia ()
916 return media;
919 bool
920 PlaylistEntry::IsSingleFile ()
922 return parent ? parent->IsSingleFile () : false;
925 PlaylistRoot *
926 PlaylistEntry::GetRoot ()
928 Playlist *pl;
930 if (IsDisposed ())
931 return NULL;
933 if (parent == NULL) {
934 g_return_val_if_fail (GetObjectType () == Type::PLAYLISTROOT, NULL);
935 return (PlaylistRoot *) this;
938 pl = parent;
940 while (pl->parent != NULL)
941 pl = pl->parent;
943 g_return_val_if_fail (pl->GetObjectType () == Type::PLAYLISTROOT, NULL);
945 return (PlaylistRoot *) pl;
949 * Playlist
952 Playlist::Playlist (Playlist *parent, IMediaSource *source)
953 : PlaylistEntry (Type::PLAYLIST, parent)
955 is_single_file = false;
956 waiting = false;
957 opened = false;
958 Init ();
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;
968 Init ();
970 AddEntry (new PlaylistEntry (this));
973 void
974 Playlist::Init ()
976 LOG_PLAYLIST ("Playlist::Init ()\n");
978 entries = new List ();
979 current_node = NULL;
980 source = NULL;
983 void
984 Playlist::Dispose ()
986 PlaylistNode *node;
987 PlaylistEntry *entry;
989 LOG_PLAYLIST ("Playlist::Dispose () id: %i\n", GET_OBJ_ID (this));
991 current_node = NULL;
993 if (entries != NULL) {
994 node = (PlaylistNode *) entries->First ();
995 while (node != NULL) {
996 entry = node->GetEntry ();
997 if (entry != NULL)
998 entry->Dispose ();
999 node = (PlaylistNode *) node->next;
1001 delete entries;
1002 entries = NULL;
1005 if (source) {
1006 source->unref ();
1007 source = NULL;
1010 PlaylistEntry::Dispose ();
1014 bool
1015 Playlist::IsCurrentEntryLastEntry ()
1017 PlaylistEntry *entry;
1018 Playlist *pl;
1020 if (entries->Last () == NULL)
1021 return false;
1023 if (current_node != entries->Last ())
1024 return false;
1026 entry = GetCurrentEntry ();
1028 if (!entry->IsPlaylist ())
1029 return true;
1031 pl = (Playlist *) entry;
1033 return pl->IsCurrentEntryLastEntry ();
1036 void
1037 Playlist::Open ()
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 ();
1054 if (current_entry)
1055 current_entry->Open ();
1057 opened = true;
1059 LOG_PLAYLIST ("Playlist::Open (): current node: %p, current entry: %p\n", current_entry, GetCurrentEntry ());
1062 bool
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);
1072 if (!current_node)
1073 return false;
1075 SetWaiting (false);
1077 current_entry = GetCurrentEntry ();
1079 if (current_entry->HasDuration() && current_entry->GetDuration()->IsForever ()) {
1080 element->SetPlayRequested ();
1081 current_entry->Play ();
1082 return true;
1085 if (current_entry->IsPlaylist ()) {
1086 Playlist *current_playlist = (Playlist *) current_entry;
1087 if (current_playlist->PlayNext ())
1088 return true;
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 ();
1100 return true;
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);
1109 return false;
1112 void
1113 Playlist::OnEntryEnded ()
1115 LOG_PLAYLIST ("Playlist::OnEntryEnded ()\n");
1116 PlayNext ();
1119 void
1120 Playlist::OnEntryFailed (ErrorEventArgs *args)
1122 bool fatal = true;
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
1132 if (args == NULL) {
1133 fatal = true;
1134 } else {
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) {
1141 // we're a playlist
1142 if (args->GetExtendedCode() == MEDIA_UNKNOWN_CODEC) {
1143 fatal = false;
1144 } else {
1145 fatal = true;
1147 } else {
1148 // we're not a playlist
1149 fatal = true;
1152 if (demuxer)
1153 demuxer->unref ();
1156 if (fatal) {
1157 if (args)
1158 args->ref ();
1159 root->Emit (PlaylistRoot::MediaErrorEvent, args);
1160 } else {
1161 root->PlayNext ();
1165 void
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);
1179 void
1180 Playlist::Play ()
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 ());
1192 OnEntryEnded ();
1193 } else {
1194 if (current_entry)
1195 current_entry->Play ();
1199 void
1200 Playlist::Pause ()
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 ();
1213 void
1214 Playlist::Stop ()
1216 PlaylistNode *node;
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;
1228 void
1229 Playlist::PopulateMediaAttributes ()
1231 PlaylistEntry *current_entry = GetCurrentEntry ();
1233 LOG_PLAYLIST ("Playlist::PopulateMediaAttributes ()\n");
1235 if (!current_entry)
1236 return;
1238 current_entry->PopulateMediaAttributes ();
1241 void
1242 Playlist::AddEntry (PlaylistEntry *entry)
1244 PlaylistNode *node;
1246 LOG_PLAYLIST ("Playlist::AddEntry (%p) Count: %i\n", entry, entries->Length ());
1248 node = new PlaylistNode (entry);
1249 entries->Append (node);
1250 entry->unref ();
1252 if (entries->Length () == 1) {
1253 g_return_if_fail (current_node == NULL);
1254 current_node = node;
1258 bool
1259 Playlist::ReplaceCurrentEntry (Playlist *pl)
1261 bool result;
1263 PlaylistEntry *current_entry = GetCurrentEntry ();
1265 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntry (%p)\n", pl);
1267 // check for too nested playlist
1268 int counter = 0;
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)
1277 counter++;
1279 if (demuxer)
1280 demuxer->unref ();
1282 e = e->GetParent ();
1284 if (counter > 5) {
1285 ErrorEventArgs *args = new ErrorEventArgs (MediaError,
1286 MoonError (MoonError::EXCEPTION, 4001, "AG_E_NETWORK_ERROR"));
1287 OnEntryFailed (args);
1288 args->unref ();
1289 return false;
1293 if (current_entry->IsPlaylist ()) {
1294 result = ((Playlist *) current_entry)->ReplaceCurrentEntry (pl);
1295 } else {
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);
1301 current_node = pln;
1302 result = true;
1305 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntrY (%p) [DONE]\n", pl);
1307 return result;
1310 void
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 ();
1328 PlaylistEntry *
1329 Playlist::GetCurrentPlaylistEntry ()
1331 PlaylistEntry *result = NULL;
1333 if (current_node)
1334 result = current_node->GetEntry () ->GetCurrentPlaylistEntry ();
1336 return result;
1339 * PlaylistRoot
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);
1350 mplayer->ref ();
1353 void
1354 PlaylistRoot::Dispose ()
1356 if (mplayer != NULL) {
1357 mplayer->RemoveAllHandlers (this);
1358 mplayer->unref ();
1359 mplayer = NULL;
1362 Playlist::Dispose ();
1365 bool
1366 PlaylistRoot::IsSingleFile ()
1368 PlaylistEntry *entry;
1370 if (GetCount () != 1)
1371 return false;
1373 entry = GetCurrentEntry ();
1374 if (entry == NULL)
1375 return false;
1377 if (entry->GetObjectType () == Type::PLAYLISTENTRY)
1378 return true;
1380 return entry->IsSingleFile ();
1383 #if DEBUG
1384 void
1385 PlaylistEntry::DumpInternal (int tabs)
1387 printf ("%*s%s %i\n", tabs, "", GetTypeName (), GET_OBJ_ID (this));
1388 tabs++;
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)");
1393 if (media) {
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");
1398 if (demuxer)
1399 demuxer->unref ();
1404 void
1405 Playlist::DumpInternal (int tabs)
1407 PlaylistNode *node;
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;
1419 void
1420 PlaylistRoot::Dump ()
1422 printf ("\n\nDUMP OF PLAYLIST\n\n");
1423 DumpInternal (0);
1424 printf ("\n\nDUMP OF PLAYLIST DONE\n\n");
1426 #endif
1428 void
1429 PlaylistRoot::SeekCallback (EventObject *obj)
1431 PlaylistRoot *playlist = (PlaylistRoot *) obj;
1432 PtsNode *pts_node;
1434 LOG_PLAYLIST ("PlaylistRoot::SeekCallback ()\n");
1436 if (playlist->IsDisposed ())
1437 return;
1439 pts_node = (PtsNode *) playlist->seeks.First ();
1440 if (pts_node != NULL) {
1441 playlist->seeks.Unlink (pts_node);
1442 playlist->Seek (pts_node->pts);
1443 delete pts_node;
1447 void
1448 PlaylistRoot::SeekAsync (guint64 pts)
1450 LOG_PLAYLIST ("PlaylistRoot::SeekAsync (%" G_GUINT64_FORMAT ")\n", pts);
1451 seeks.Append (new PtsNode (pts));
1452 AddTickCall (SeekCallback);
1455 void
1456 PlaylistRoot::PlayCallback (EventObject *obj)
1458 LOG_PLAYLIST ("Playlist::PlayCallback ()\n");
1460 PlaylistRoot *root = (PlaylistRoot *) obj;
1461 if (root->IsDisposed ())
1462 return;
1463 root->Play ();
1466 void
1467 PlaylistRoot::PlayAsync ()
1469 LOG_PLAYLIST ("Playlist::PlayAsync ()\n");
1470 AddTickCall (PlayCallback);
1473 void
1474 PlaylistRoot::PauseCallback (EventObject *obj)
1476 LOG_PLAYLIST ("Playlist::PauseCallback ()\n");
1478 PlaylistRoot *root = (PlaylistRoot *) obj;
1479 if (root->IsDisposed ())
1480 return;
1481 root->Pause ();
1484 void
1485 PlaylistRoot::PauseAsync ()
1487 LOG_PLAYLIST ("Playlist::PauseAsync ()\n");
1488 AddTickCall (PauseCallback);
1491 void
1492 PlaylistRoot::OpenCallback (EventObject *obj)
1494 LOG_PLAYLIST ("Playlist::OpenCallback ()\n");
1496 PlaylistRoot *root = (PlaylistRoot *) obj;
1497 if (root->IsDisposed ())
1498 return;
1499 root->Open ();
1502 void
1503 PlaylistRoot::OpenAsync ()
1505 LOG_PLAYLIST ("Playlist::OpenAsync ()\n");
1506 AddTickCall (OpenCallback);
1509 void
1510 PlaylistRoot::StopCallback (EventObject *obj)
1512 LOG_PLAYLIST ("Playlist::StopCallback ()\n");
1514 PlaylistRoot *root = (PlaylistRoot *) obj;
1515 if (root->IsDisposed ())
1516 return;
1517 root->Stop ();
1520 void
1521 PlaylistRoot::StopAsync ()
1523 LOG_PLAYLIST ("Playlist::StopAsync ()\n");
1524 AddTickCall (StopCallback);
1527 void
1528 PlaylistRoot::Stop ()
1530 MediaPlayer *mplayer;
1532 LOG_PLAYLIST ("PlaylistRoot::Stop ()\n");
1534 mplayer = GetMediaPlayer ();
1536 Playlist::Stop ();
1537 if (mplayer != NULL)
1538 mplayer->Stop ();
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:
1541 // Stop ();
1542 // Play ();
1543 // would end up like:
1544 // StopAsync (); -> enqueue Stop
1545 // PlayAsync (); -> enqueue Play
1546 // Stop is called, enqueue Open
1547 Open ();
1548 Emit (StopEvent); // we emit the event after enqueuing the Open request, do avoid funky side-effects of event emission.
1551 void
1552 PlaylistRoot::EmitBufferUnderflowEvent (EventObject *obj)
1554 PlaylistRoot *root = (PlaylistRoot *) obj;
1555 root->Emit (BufferUnderflowEvent);
1558 MediaPlayer *
1559 PlaylistRoot::GetMediaPlayer ()
1561 return mplayer;
1564 Media *
1565 PlaylistRoot::GetCurrentMedia ()
1567 PlaylistEntry *entry = GetCurrentEntry ();
1569 if (entry == NULL)
1570 return NULL;
1572 return entry->GetMedia ();
1575 MediaElement *
1576 PlaylistRoot::GetElement ()
1578 return element;
1581 void
1582 PlaylistRoot::MediaEndedHandler (MediaPlayer *mplayer, EventArgs *args)
1584 LOG_PLAYLIST ("PlaylistRoot::MediaEndedHandler (%p, %p)\n", mplayer, args);
1586 OnEntryEnded ();
1588 // Emit (MediaEndedEvent, args);
1591 void
1592 PlaylistRoot::BufferUnderflowHandler (MediaPlayer *mplayer, EventArgs *args)
1594 LOG_PLAYLIST ("PlaylistRoot::BufferUnderflowHandler (%p, %p)\n", mplayer, args);
1596 if (Surface::InMainThread ()) {
1597 EmitBufferUnderflowEvent (this);
1598 } else {
1599 AddTickCall (EmitBufferUnderflowEvent);
1604 * PlaylistParser
1607 PlaylistParser::PlaylistParser (PlaylistRoot *root, IMediaSource *source)
1609 this->root = root;
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;
1619 void
1620 PlaylistParser::SetSource (IMediaSource *new_source)
1622 if (source)
1623 source->unref ();
1624 source = new_source;
1625 if (source)
1626 source->ref ();
1629 void
1630 PlaylistParser::Setup (XmlType type)
1632 playlist = NULL;
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);
1650 void
1651 PlaylistParser::Cleanup ()
1653 if (kind_stack) {
1654 kind_stack->Clear (true);
1655 delete kind_stack;
1656 kind_stack = NULL;
1658 delete internal;
1659 internal = NULL;
1660 if (playlist) {
1661 playlist->unref ();
1662 playlist = NULL;
1664 if (error_args) {
1665 error_args->unref ();
1666 error_args = NULL;
1670 PlaylistParser::~PlaylistParser ()
1672 Cleanup ();
1675 static bool
1676 str_match (const char *candidate, const char *tag)
1678 return g_ascii_strcasecmp (candidate, tag) == 0;
1681 void
1682 PlaylistParser::on_asx_start_element (gpointer user_data, const char *name, const char **attrs)
1684 ((PlaylistParser *) user_data)->OnASXStartElement (name, attrs);
1687 void
1688 PlaylistParser::on_asx_end_element (gpointer user_data, const char *name)
1690 ((PlaylistParser *) user_data)->OnASXEndElement (name);
1693 void
1694 PlaylistParser::on_asx_text (gpointer user_data, const char *data, int len)
1696 ((PlaylistParser *) user_data)->OnASXText (data, len);
1699 static bool
1700 is_all_whitespace (const char *str)
1702 if (str == NULL)
1703 return true;
1705 for (int i = 0; str [i] != 0; i++) {
1706 switch (str [i]) {
1707 case 10:
1708 case 13:
1709 case ' ':
1710 case '\t':
1711 break;
1712 default:
1713 return false;
1716 return true;
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
1727 // up till then
1729 // At most 4 digits of fract is read, the rest is ignored (even if it's not numbers).
1731 static bool
1732 parse_int (const char **pp, const char *end, int *result)
1734 const char *p = *pp;
1735 int res = 0;
1736 bool success = false;
1738 while (p <= end && g_ascii_isdigit (*p)) {
1739 res = res * 10 + *p - '0';
1740 p++;
1743 success = *pp != p;
1745 *pp = p;
1746 *result = res;
1748 return success;
1751 static bool
1752 duration_from_asx_str (PlaylistParser *parser, const char *str, Duration **res)
1754 const char *end = str + strlen (str);
1755 const char *p;
1757 int values [] = {0, 0, 0};
1758 int counter = 0;
1759 int hh = 0, mm = 0, ss = 0;
1760 int milliseconds = 0;
1761 int digits = 2;
1763 p = str;
1765 if (!g_ascii_isdigit (*p)) {
1766 parser->ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 2210, "AG_E_INVALID_ARGUMENT")));
1767 return false;
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")));
1774 return false;
1776 counter++;
1777 if (*p != ':')
1778 break;
1779 p++;
1782 if (*p == '.') {
1783 p++;
1784 while (digits >= 0 && g_ascii_isdigit (*p)) {
1785 milliseconds += pow (10.0f, digits) * (*p - '0');
1786 p++;
1787 digits--;
1789 if (counter == 3 && *p != 0 && !g_ascii_isdigit (*p)) {
1790 parser->ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 2210, "AG_E_INVALID_ARGUMENT")));
1791 return false;
1795 switch (counter) {
1796 case 1:
1797 ss = values [0];
1798 break;
1799 case 2:
1800 ss = values [1];
1801 mm = values [0];
1802 break;
1803 case 3:
1804 ss = values [2];
1805 mm = values [1];
1806 hh = values [0];
1807 break;
1808 default:
1809 parser->ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 2210, "AG_E_INVALID_ARGUMENT")));
1810 return false;
1813 gint64 ms = ((hh * 3600) + (mm * 60) + ss) * 1000 + milliseconds;
1814 TimeSpan result = TimeSpan_FromPts (MilliSeconds_ToPts (ms));
1815 Duration *duration = new Duration (result);
1817 *res = duration;
1819 return true;
1822 void
1823 PlaylistParser::OnASXStartElement (const char *name, const char **attrs)
1825 PlaylistKind::Kind kind = StringToKind (name);
1826 Uri *uri = NULL;
1827 bool failed;
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);
1836 switch (kind) {
1837 case PlaylistKind::Abstract:
1838 if (attrs != NULL && attrs [0] != NULL)
1839 ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1840 break;
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")));
1845 return;
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;
1856 } else {
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")));
1866 } else {
1867 ParsingError (new ErrorEventArgs (MediaError,
1868 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1871 break;
1872 case PlaylistKind::Author:
1873 if (attrs != NULL && attrs [0] != NULL)
1874 ParsingError (new ErrorEventArgs (MediaError,
1875 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1876 break;
1877 case PlaylistKind::Banner:
1878 if (attrs != NULL && attrs [0] != NULL)
1879 ParsingError (new ErrorEventArgs (MediaError,
1880 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1881 break;
1882 case PlaylistKind::Base:
1883 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
1884 break;
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) {
1889 failed = false;
1890 uri = new Uri ();
1891 if (!uri->Parse (attrs [i+1], true)) {
1892 failed = true;
1893 } else if (uri->GetScheme() == NULL) {
1894 failed = true;
1895 } else if (uri->IsScheme ("http") &&
1896 uri->IsScheme ("https") &&
1897 uri->IsScheme ("mms") &&
1898 uri->IsScheme ("rtsp") &&
1899 uri->IsScheme ("rstpt")) {
1900 failed = true;
1903 if (!failed) {
1904 GetCurrentContent ()->SetBase (uri);
1905 } else {
1906 delete uri;
1907 ParsingError (new ErrorEventArgs (MediaError,
1908 MoonError (MoonError::EXCEPTION, 4001, "AG_E_NETWORK_ERROR")));
1910 uri = NULL;
1912 } else {
1913 ParsingError (new ErrorEventArgs (MediaError,
1914 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1915 break;
1918 break;
1919 case PlaylistKind::Copyright:
1920 if (attrs != NULL && attrs [0] != NULL)
1921 ParsingError (new ErrorEventArgs (MediaError,
1922 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1923 break;
1924 case PlaylistKind::Duration: {
1925 Duration *dur;
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);
1934 } else {
1935 ParsingError (new ErrorEventArgs (MediaError,
1936 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1937 break;
1940 break;
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")) {
1948 client_skip = true;
1949 } else if (str_match (attrs [i+1], "NO")) {
1950 client_skip = false;
1951 } else {
1952 ParsingError (new ErrorEventArgs (MediaError,
1953 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
1954 break;
1956 } else if (str_match (attrs [i], "SKIPIFREF")) {
1957 ParsingError (new ErrorEventArgs (MediaError,
1958 MoonError (MoonError::EXCEPTION, 3007, "Unsupported ASX attribute")));
1959 break;
1960 } else {
1961 ParsingError (new ErrorEventArgs (MediaError,
1962 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1963 break;
1966 PlaylistEntry *entry = new PlaylistEntry (playlist);
1967 entry->SetClientSkip (client_skip);
1968 playlist->AddEntry (entry);
1969 current_entry = entry;
1970 break;
1972 case PlaylistKind::EntryRef: {
1973 char *href = NULL;
1974 for (int i = 0; attrs [i] != NULL; i += 2) {
1975 if (str_match (attrs [i], "HREF")) {
1976 if (href == NULL)
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?
1981 } else {
1982 ParsingError (new ErrorEventArgs (MediaError,
1983 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1984 break;
1988 if (href) {
1989 uri = new Uri ();
1990 if (!uri->Parse (href)) {
1991 delete uri;
1992 uri = NULL;
1993 ParsingError (new ErrorEventArgs (MediaError,
1994 MoonError (MoonError::EXCEPTION, 1001, "AG_E_UNKNOWN_ERROR")));
1998 PlaylistEntry *entry = new PlaylistEntry (playlist);
1999 if (uri)
2000 entry->SetSourceName (uri);
2001 uri = NULL;
2002 playlist->AddEntry (entry);
2003 current_entry = entry;
2004 break;
2006 case PlaylistKind::LogUrl:
2007 if (attrs != NULL && attrs [0] != NULL)
2008 ParsingError (new ErrorEventArgs (MediaError,
2009 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2010 break;
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]);
2019 } else {
2020 ParsingError (new ErrorEventArgs (MediaError,
2021 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2022 break;
2025 break;
2026 case PlaylistKind::StartTime: {
2027 Duration *dur;
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 ());
2036 } else {
2037 ParsingError (new ErrorEventArgs (MediaError,
2038 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2039 break;
2043 break;
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) {
2049 uri = new Uri ();
2050 if (uri->Parse (attrs [i+1])) {
2051 GetCurrentEntry ()->SetSourceName (uri);
2052 } else {
2053 delete uri;
2054 ParsingError (new ErrorEventArgs (MediaError,
2055 MoonError (MoonError::EXCEPTION, 1001, "AG_E_UNKNOWN_ERROR")));
2057 uri = NULL;
2059 } else {
2060 ParsingError (new ErrorEventArgs (MediaError,
2061 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2062 break;
2065 break;
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];
2076 } else {
2077 ParsingError (new ErrorEventArgs (MediaError,
2078 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2079 break;
2082 if (value != NULL && value [0] != 0 && name != NULL && name [0] != 0) {
2083 PlaylistEntry *entry = GetCurrentEntry ();
2084 if (entry == NULL)
2085 entry = playlist;
2086 if (entry == NULL)
2087 entry = root;
2088 entry->AddParams (name, value);
2089 } else {
2090 // TODO: add test
2092 break;
2094 case PlaylistKind::Title:
2095 if (attrs != NULL && attrs [0] != NULL)
2096 ParsingError (new ErrorEventArgs (MediaError,
2097 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2098 break;
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")));
2105 break;
2106 case PlaylistKind::Root:
2107 case PlaylistKind::Unknown:
2108 default:
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")));
2112 break;
2116 void
2117 PlaylistParser::OnASXEndElement (const char *name)
2119 PlaylistKind::Kind kind = GetCurrentKind ();
2120 Duration *dur;
2122 LOG_PLAYLIST ("PlaylistParser::OnEndElement (%s), GetCurrentKind (): %d, GetCurrentKind () to string: %s\n", name, kind, KindToString (kind));
2124 switch (kind) {
2125 case PlaylistKind::Abstract:
2126 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2127 break;
2128 if (GetCurrentContent () != NULL)
2129 GetCurrentContent ()->SetAbstract (current_text);
2130 break;
2131 case PlaylistKind::Author:
2132 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2133 break;
2134 if (GetCurrentContent () != NULL)
2135 GetCurrentContent ()->SetAuthor (current_text);
2136 break;
2137 case PlaylistKind::Base:
2138 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2139 break;
2140 break;
2141 case PlaylistKind::Copyright:
2142 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2143 break;
2144 if (GetCurrentContent () != NULL)
2145 GetCurrentContent ()->SetCopyright (current_text);
2146 break;
2147 case PlaylistKind::Duration:
2148 if (!AssertParentKind (PlaylistKind::Entry | PlaylistKind::Ref))
2149 break;
2150 if (current_text == NULL)
2151 break;
2152 duration_from_asx_str (this, current_text, &dur);
2153 if (GetCurrentEntry () != NULL)
2154 GetCurrentEntry ()->SetDuration (dur);
2155 break;
2156 case PlaylistKind::Entry:
2157 if (!AssertParentKind (PlaylistKind::Asx))
2158 break;
2159 if (!is_all_whitespace (current_text)) {
2160 ParsingError (new ErrorEventArgs (MediaError,
2161 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2163 break;
2164 case PlaylistKind::EntryRef:
2165 if (!AssertParentKind (PlaylistKind::Asx))
2166 break;
2167 break;
2168 case PlaylistKind::StartTime:
2169 if (!AssertParentKind (PlaylistKind::Entry | PlaylistKind::Ref))
2170 break;
2171 if (!is_all_whitespace (current_text)) {
2172 ParsingError (new ErrorEventArgs (MediaError,
2173 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2175 break;
2176 case PlaylistKind::Title:
2177 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2178 break;
2179 if (GetCurrentContent () != NULL)
2180 GetCurrentContent ()->SetTitle (current_text);
2181 break;
2182 case PlaylistKind::Asx:
2183 if (playlist_version == 3)
2184 was_playlist = true;
2185 if (!AssertParentKind (PlaylistKind::Root))
2186 break;
2187 break;
2188 case PlaylistKind::Ref:
2189 if (!AssertParentKind (PlaylistKind::Entry))
2190 break;
2191 if (!is_all_whitespace (current_text)) {
2192 ParsingError (new ErrorEventArgs (MediaError,
2193 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2195 break;
2196 case PlaylistKind::MoreInfo:
2197 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2198 break;
2199 if (!is_all_whitespace (current_text)) {
2200 ParsingError (new ErrorEventArgs (MediaError,
2201 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2203 break;
2204 case PlaylistKind::Param:
2205 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2206 break;
2207 if (!is_all_whitespace (current_text)) {
2208 ParsingError (new ErrorEventArgs (MediaError,
2209 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2211 break;
2212 default:
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")));
2216 break;
2219 if (current_text != NULL) {
2220 g_free (current_text);
2221 current_text = NULL;
2224 switch (GetCurrentKind ()) {
2225 case PlaylistKind::Entry:
2226 EndEntry ();
2227 break;
2228 default:
2229 break;
2231 PopCurrentKind ();
2234 void
2235 PlaylistParser::OnASXText (const char *text, int len)
2237 char *a = g_strndup (text, len);
2239 #if DEBUG
2240 char *p = g_strndup (text, len);
2241 for (int i = 0; p [i] != 0; i++)
2242 if (p [i] == 10 || p [i] == 13)
2243 p [i] = ' ';
2245 LOG_PLAYLIST ("PlaylistParser::OnText (%s, %d)\n", p, len);
2246 g_free (p);
2247 #endif
2249 if (current_text == NULL) {
2250 current_text = a;
2251 } else {
2252 char *b = g_strconcat (current_text, a, NULL);
2253 g_free (current_text);
2254 current_text = b;
2258 bool
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];
2265 do {
2266 result = source->Peek ((guint8 *) buffer, asx_header_length);
2267 if (!result)
2268 goto cleanup;
2270 // skip any whitespace
2271 unsigned char c = buffer [0];
2272 switch (c) {
2273 case ' ':
2274 case '\t':
2275 case 10:
2276 case 13: {
2277 result = source->ReadAll ((guint8 *) buffer, 1);
2278 if (!result)
2279 goto cleanup;
2280 continue;
2282 case 0xef: {
2283 if (buffer [1] == 0xbb && buffer [2] == 0xbf) { // UTF-8 BOM: EF BB BF
2284 result = source->ReadAll ((guint8 *) buffer, 3);
2285 if (!result)
2286 goto cleanup;
2287 continue;
2289 // TODO: there might be other BOMs we should handle too
2290 // fall through
2292 default:
2293 result = !g_ascii_strncasecmp ((const char *) buffer, asx_header, asx_header_length);
2294 goto cleanup;
2296 } while (true);
2298 cleanup:
2300 source->Seek (0, SEEK_SET);
2302 return result;
2305 bool
2306 PlaylistParser::IsASX3 (IMediaSource *source)
2308 return Is (source, "<ASX");
2311 bool
2312 PlaylistParser::IsASX2 (IMediaSource *source)
2314 return Is (source, "[Reference]");
2317 bool
2318 PlaylistParser::ParseASX2 ()
2320 const int BUFFER_SIZE = 1024;
2321 int bytes_read;
2322 char buffer[BUFFER_SIZE];
2323 char *ref;
2324 char *mms_uri;
2325 GKeyFile *key_file;
2326 Uri *uri;
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");
2333 return false;
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);
2341 return false;
2344 ref = g_key_file_get_value (key_file, "Reference", "Ref1", NULL);
2345 if (ref == NULL) {
2346 LOG_PLAYLIST_WARN ("Could not find Ref1 entry in asx2 document.\n");
2347 g_key_file_free (key_file);
2348 return false;
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");
2353 g_free (ref);
2354 g_key_file_free (key_file);
2355 return false;
2358 mms_uri = g_strdup_printf ("mms://%s", strstr (ref, "http://") + strlen ("http://"));
2359 g_free (ref);
2360 g_key_file_free (key_file);
2363 playlist = new Playlist (root, source);
2365 PlaylistEntry *entry = new PlaylistEntry (playlist);
2366 uri = new Uri ();
2367 if (uri->Parse (mms_uri)) {
2368 entry->SetSourceName (uri);
2369 } else {
2370 delete uri;
2372 playlist->AddEntry (entry);
2373 current_entry = entry;
2375 return true;
2378 bool
2379 PlaylistParser::TryFixError (gint8 *current_buffer, int bytes_read, int total_bytes_read)
2381 Media *media;
2383 if (XML_GetErrorCode (internal->parser) != XML_ERROR_INVALID_TOKEN)
2384 return false;
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)
2390 return false;
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]) {
2403 case '&':
2404 escape = g_strdup ("&amp;");
2405 break;
2406 case '<':
2407 escape = g_strdup ("&lt;");
2408 break;
2409 case '>':
2410 escape = g_strdup ("&gt;");
2411 break;
2412 case '\"':
2413 break;
2415 if (escape)
2416 break;
2417 index--;
2420 if (!escape) {
2421 LOG_PLAYLIST_WARN ("Unable to find an invalid escape character to fix in ASX: %s.\n", current_buffer);
2422 g_free (escape);
2423 return false;
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;
2449 if (error_args) {
2450 // Clear out errors in the old buffer
2451 error_args->unref ();
2452 error_args = NULL;
2456 g_free (escape);
2458 if (media)
2459 media->unref ();
2461 return true;
2464 MediaResult
2465 PlaylistParser::Parse ()
2467 bool result;
2468 gint64 last_available_pos;
2469 gint64 size;
2471 LOG_PLAYLIST ("PlaylistParser::Parse ()\n");
2473 do {
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 ();
2489 } else {
2490 result = false;
2492 } while (result && internal->reparse);
2494 return result ? MEDIA_SUCCESS : MEDIA_FAIL;
2497 bool
2498 PlaylistParser::ParseASX3 ()
2500 int bytes_read;
2501 int total_bytes_read = 0;
2502 void *buffer;
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;
2507 for (;;) {
2508 buffer = XML_GetBuffer(internal->parser, BUFFER_SIZE);
2509 if (buffer == NULL) {
2510 fprintf (stderr, "Could not allocate memory for asx document parsing.\n");
2511 return false;
2514 bytes_read = source->ReadSome (buffer, BUFFER_SIZE);
2515 if (bytes_read < 0) {
2516 fprintf (stderr, "Could not read asx document for parsing.\n");
2517 return false;
2520 total_bytes_read += bytes_read;
2521 if (!XML_ParseBuffer (internal->parser, bytes_read, bytes_read == 0)) {
2522 if (error_args != NULL)
2523 return false;
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")));
2529 return false;
2530 case XML_ERROR_DUPLICATE_ATTRIBUTE:
2531 ParsingError (new ErrorEventArgs (MediaError,
2532 MoonError (MoonError::EXCEPTION, 7031, "wfc: unique attribute spec")));
2533 return false;
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))
2539 return true;
2540 // fall through
2541 default:
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)));
2547 g_free (msg);
2548 return false;
2552 if (bytes_read == 0)
2553 break;
2555 internal->bytes_read += bytes_read;
2558 return playlist != NULL;
2561 PlaylistEntry *
2562 PlaylistParser::GetCurrentContent ()
2564 if (current_entry != NULL)
2565 return current_entry;
2567 return playlist;
2570 PlaylistEntry *
2571 PlaylistParser::GetCurrentEntry ()
2573 return current_entry;
2576 void
2577 PlaylistParser::EndEntry ()
2579 this->current_entry = NULL;
2582 void
2583 PlaylistParser::PushCurrentKind (PlaylistKind::Kind kind)
2585 kind_stack->Append (new KindNode (kind));
2586 LOG_PLAYLIST ("PlaylistParser::Push (%d)\n", kind);
2589 void
2590 PlaylistParser::PopCurrentKind ()
2592 LOG_PLAYLIST ("PlaylistParser::PopCurrentKind (), current: %d\n", ((KindNode *)kind_stack->Last ())->kind);
2593 kind_stack->Remove (kind_stack->Last ());
2596 PlaylistKind::Kind
2597 PlaylistParser::GetCurrentKind ()
2599 KindNode *node = (KindNode *) kind_stack->Last ();
2600 return node->kind;
2603 PlaylistKind::Kind
2604 PlaylistParser::GetParentKind ()
2606 KindNode *node = (KindNode *) kind_stack->Last ()->prev;
2607 return node->kind;
2610 bool
2611 PlaylistParser::AssertParentKind (int kind)
2613 LOG_PLAYLIST ("PlaylistParser::AssertParentKind (%d), GetParentKind: %d, result: %d\n", kind, GetParentKind (), GetParentKind () & kind);
2615 if (GetParentKind () & kind)
2616 return true;
2618 ParsingError (new ErrorEventArgs (MediaError,
2619 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2621 return false;
2624 void
2625 PlaylistParser::ParsingError (ErrorEventArgs *args)
2627 LOG_PLAYLIST ("PlaylistParser::ParsingError (%s)\n", args->GetErrorMessage());
2629 XML_StopParser (internal->parser, false);
2630 if (error_args) {
2631 if (args)
2632 args->unref ();
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 [] = {
2640 /* ASX3 */
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)
2665 PlaylistKind::Kind
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;
2673 break;
2677 LOG_PLAYLIST ("PlaylistParser::StringToKind ('%s') = %d\n", str, kind);
2679 return kind;
2682 const char *
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;
2690 break;
2694 LOG_PLAYLIST ("PlaylistParser::KindToString (%d) = '%s'\n", kind, result);
2696 return result;