add the 2.1-bootstrap dir to MONO_PATH when running smcs
[moon.git] / src / playlist.cpp
blob11cd94ff6baec2242f8d671f355492f618998b88
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)
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)
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)
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;
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->GetDemuxer ();
320 g_return_if_fail (demuxer != NULL);
322 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p) demuxer: %i %s\n", media, args, GET_OBJ_ID (demuxer), demuxer->GetTypeName ());
324 if (demuxer->IsPlaylist ()) {
325 playlist = demuxer->GetPlaylist ();
327 g_return_if_fail (playlist != NULL);
328 g_return_if_fail (parent != NULL);
330 parent->ReplaceCurrentEntry (playlist);
331 playlist->Open ();
332 } else {
333 if (parent->GetCurrentEntry () == this) {
334 OpenMediaPlayer ();
335 } else {
336 LOG_PLAYLIST ("PlaylistEntry::OpenCompletedHandler (%p, %p): opened entry in advance, waiting for current entry to finish.\n", media, args);
342 void
343 PlaylistEntry::SeekingHandler (Media *media, EventArgs *args)
345 LOG_PLAYLIST ("PlaylistEntry::SeekingHandler (%p, %p)\n", media, args);
348 void
349 PlaylistEntry::SeekCompletedHandler (Media *media, EventArgs *args)
351 PlaylistRoot *root = GetRoot ();
353 LOG_PLAYLIST ("PlaylistEntry::SeekCompletedHandler (%p, %p)\n", media, args);
355 g_return_if_fail (root != NULL);
357 if (args)
358 args->ref ();
359 root->Emit (PlaylistRoot::SeekCompletedEvent, args);
362 void
363 PlaylistEntry::CurrentStateChangedHandler (Media *media, EventArgs *args)
365 LOG_PLAYLIST ("PlaylistEntry::CurrentStateChangedHandler (%p, %p)\n", media, args);
368 void
369 PlaylistEntry::MediaErrorHandler (Media *media, ErrorEventArgs *args)
371 LOG_PLAYLIST ("PlaylistEntry::MediaErrorHandler (%p, %p): %s '%s'\n", media, args, GetFullSourceName (), args ? args->error_message : "?");
373 g_return_if_fail (parent != NULL);
375 parent->OnEntryFailed (args);
378 void
379 PlaylistEntry::DownloadProgressChangedHandler (Media *media, EventArgs *args)
381 PlaylistRoot *root;
383 LOG_PLAYLIST ("PlaylistEntry::DownloadProgressChanged (%p, %p %.2f). Disposed: %i\n", media, args, args ? ((ProgressEventArgs *) args)->progress : -1.0, IsDisposed ());
385 if (IsDisposed ())
386 return;
388 root = GetRoot ();
390 g_return_if_fail (root != NULL);
392 if (args)
393 args->ref ();
394 root->Emit (PlaylistRoot::DownloadProgressChangedEvent, args);
397 void
398 PlaylistEntry::BufferingProgressChangedHandler (Media *media, EventArgs *args)
400 PlaylistRoot *root = GetRoot ();
402 LOG_PLAYLIST ("PlaylistEntry::BufferingProgressChanged (%p, %p) %.2f\n", media, args, args ? ((ProgressEventArgs *) args)->progress : -1.0);
404 if (root == NULL)
405 return; // this might happen if the media is still buffering and we're in the process of getting cleaned up
407 if (args)
408 args->ref ();
409 root->Emit (PlaylistRoot::BufferingProgressChangedEvent, args);
412 void
413 PlaylistEntry::Seek (guint64 pts)
415 LOG_PLAYLIST ("PlaylistEntry::Seek (%" G_GUINT64_FORMAT ")\n", pts);
417 g_return_if_fail (media != NULL);
419 media->SeekAsync (pts);
422 void
423 PlaylistEntry::AddParams (const char *name, const char *value)
425 char *uppername = g_ascii_strup (name, strlen (name));
426 if (!strcmp (uppername, "AUTHOR")) {
427 SetAuthor (value);
428 } else if (!strcmp (uppername, "ABSTRACT")) {
429 SetAbstract (value);
430 } else if (!strcmp (uppername, "TITLE")) {
431 SetTitle (value);
432 } else if (!strcmp (uppername, "COPYRIGHT")) {
433 SetCopyright (value);
434 } else if (!strcmp (uppername, "INFOTARGET")) {
435 SetInfoTarget (value);
436 } else if (!strcmp (uppername, "INFOURL")) {
437 SetInfoURL (value);
438 } else {
439 if (params == NULL)
440 params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
442 if (g_hash_table_lookup (params, uppername) == NULL) {
443 g_hash_table_insert (params, uppername, g_strdup (value));
444 uppername = NULL;
447 g_free (uppername);
450 Uri *
451 PlaylistEntry::GetBase ()
453 return base;
456 Uri *
457 PlaylistEntry::GetBaseInherited ()
459 if (base != NULL)
460 return base;
461 if (parent != NULL)
462 return parent->GetBaseInherited ();
463 return NULL;
466 void
467 PlaylistEntry::SetBase (Uri *base)
469 // TODO: Haven't been able to make BASE work with SL,
470 // which means that I haven't been able to confirm any behaviour.
471 if (!(set_values & PlaylistKind::Base)) {
472 this->base = base;
473 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Base);
474 } else {
475 delete base;
479 const char *
480 PlaylistEntry::GetTitle ()
482 return title;
485 void
486 PlaylistEntry::SetTitle (const char *title)
488 if (!(set_values & PlaylistKind::Title)) {
489 this->title = g_strdup (title);
490 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Title);
494 const char *
495 PlaylistEntry::GetAuthor ()
497 return author;
500 void PlaylistEntry::SetAuthor (const char *author)
502 if (!(set_values & PlaylistKind::Author)) {
503 this->author = g_strdup (author);
504 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Author);
508 const char *
509 PlaylistEntry::GetAbstract ()
511 return abstract;
514 void
515 PlaylistEntry::SetAbstract (const char *abstract)
517 if (!(set_values & PlaylistKind::Abstract)) {
518 this->abstract = g_strdup (abstract);
519 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Abstract);
523 const char *
524 PlaylistEntry::GetCopyright ()
526 return copyright;
529 void
530 PlaylistEntry::SetCopyright (const char *copyright)
532 if (!(set_values & PlaylistKind::Copyright)) {
533 this->copyright = g_strdup (copyright);
534 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Copyright);
538 Uri *
539 PlaylistEntry::GetSourceName ()
541 return source_name;
544 void
545 PlaylistEntry::SetSourceName (Uri *source_name)
547 if (this->source_name)
548 delete this->source_name;
549 this->source_name = source_name;
552 TimeSpan
553 PlaylistEntry::GetStartTime ()
555 return start_time;
558 void
559 PlaylistEntry::SetStartTime (TimeSpan start_time)
561 if (!(set_values & PlaylistKind::StartTime)) {
562 this->start_time = start_time;
563 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::StartTime);
567 Duration*
568 PlaylistEntry::GetDuration ()
570 return duration;
573 Duration *
574 PlaylistEntry::GetInheritedDuration ()
576 if (HasDuration ()) {
577 return GetDuration ();
578 } else if (parent != NULL) {
579 return parent->GetInheritedDuration ();
580 } else {
581 return NULL;
585 bool
586 PlaylistEntry::HasInheritedDuration ()
588 if (HasDuration ()) {
589 return true;
590 } else if (parent != NULL) {
591 return parent->HasInheritedDuration ();
592 } else {
593 return false;
597 void
598 PlaylistEntry::SetDuration (Duration *duration)
600 if (!(set_values & PlaylistKind::Duration)) {
601 this->duration = duration;
602 set_values = (PlaylistKind::Kind) (set_values | PlaylistKind::Duration);
606 const char *
607 PlaylistEntry::GetInfoTarget ()
609 return info_target;
612 void
613 PlaylistEntry::SetInfoTarget (const char *info_target)
615 g_free (this->info_target);
616 this->info_target = g_strdup (info_target);
619 const char *
620 PlaylistEntry::GetInfoURL ()
622 return info_url;
625 void
626 PlaylistEntry::SetInfoURL (const char *info_url)
628 g_free (this->info_url);
629 this->info_url = g_strdup (info_url);
632 bool
633 PlaylistEntry::GetClientSkip ()
635 return client_skip;
638 void
639 PlaylistEntry::SetClientSkip (bool value)
641 client_skip = value;
644 MediaElement *
645 PlaylistEntry::GetElement ()
647 g_return_val_if_fail (parent != NULL, NULL);
649 return parent->GetElement ();
652 void
653 PlaylistEntry::ClearMedia ()
655 g_return_if_fail (media != NULL);
656 media->unref ();
657 media = NULL;
660 MediaPlayer *
661 PlaylistEntry::GetMediaPlayer ()
663 PlaylistRoot *root = GetRoot ();
665 g_return_val_if_fail (root != NULL, NULL);
667 return root->GetMediaPlayer ();
670 static void
671 add_attribute (MediaAttributeCollection *attributes, const char *name, const char *attr)
673 if (!attr)
674 return;
676 MediaAttribute *attribute = new MediaAttribute ();
677 attribute->SetValue (attr);
678 attribute->SetName (name);
680 attributes->Add (attribute);
681 attribute->unref ();
684 static void
685 add_attribute_glib (const char *name, const char *value, MediaAttributeCollection *attributes)
687 add_attribute (attributes, name, value);
690 void
691 PlaylistEntry::PopulateMediaAttributes ()
693 LOG_PLAYLIST ("PlaylistEntry::PopulateMediaAttributes ()\n");
695 const char *abstract = NULL;
696 const char *author = NULL;
697 const char *copyright = NULL;
698 const char *title = NULL;
699 const char *infotarget = NULL;
700 const char *infourl = NULL;
701 const char *baseurl = NULL;
703 MediaElement *element = GetElement ();
704 PlaylistEntry *current = this;
705 MediaAttributeCollection *attributes;
707 g_return_if_fail (element != NULL);
709 if (!(attributes = element->GetAttributes ())) {
710 attributes = new MediaAttributeCollection ();
711 element->SetAttributes (attributes);
712 } else {
713 attributes->Clear ();
716 while (current != NULL) {
717 if (abstract == NULL)
718 abstract = current->GetAbstract ();
719 if (author == NULL)
720 author = current->GetAuthor ();
721 if (copyright == NULL)
722 copyright = current->GetCopyright ();
723 if (title == NULL)
724 title = current->GetTitle ();
725 if (infotarget == NULL)
726 infotarget = current->GetInfoTarget ();
727 if (infourl == NULL)
728 infourl = current->GetInfoURL ();
729 if (baseurl == NULL && current->GetBase () != NULL)
730 baseurl = current->GetBase ()->originalString;
732 current = current->GetParent ();
735 add_attribute (attributes, "ABSTRACT", abstract);
736 add_attribute (attributes, "AUTHOR", author);
737 add_attribute (attributes, "BaseURL", baseurl);
738 add_attribute (attributes, "COPYRIGHT", copyright);
739 add_attribute (attributes, "InfoTarget", infotarget);
740 add_attribute (attributes, "InfoURL", infourl);
741 add_attribute (attributes, "TITLE", title);
743 current = this;
744 while (current != NULL) {
745 if (current->params != NULL)
746 g_hash_table_foreach (current->params, (GHFunc) add_attribute_glib, attributes);
747 current = current->GetParent ();
751 const char *
752 PlaylistEntry::GetFullSourceName ()
755 * Now here we have some interesting semantics:
756 * - BASE has to be a complete url, with scheme and domain
757 * - BASE only matters up to the latest / (if no /, the entire BASE is used)
759 * Examples (numbered according to the test-playlist-with-base test in test/media/video)
761 * 01 localhost/dir/ + * = error
762 * 02 /dir/ + * = error
763 * 03 dir + * = error
764 * 04 http://localhost/dir/ + somefile = http://localhost/dir/somefile
765 * 05 http://localhost/dir + somefile = http://localhost/somefile
766 * 06 http://localhost + somefile = http://localhost/somefile
767 * 07 http://localhost/dir/ + /somefile = http://localhost/somefile
768 * 08 http://localhost/dir/ + dir2/somefile = http://localhost/dir/dir2/somefile
769 * 09 rtsp://localhost/ + somefile = http://localhost/somefile
770 * 10 mms://localhost/dir/ + somefile = mms://localhost/dir/somefile
771 * 11 http://localhost/?huh + somefile = http://localhost/somefile
772 * 12 http://localhost/#huh + somefile = http://localhost/somefile
773 * 13 httP://localhost/ + somefile = http://localhost/somefile
777 // TODO: url validation, however it should probably happen inside MediaElement when we set the source
779 if (full_source_name == NULL) {
780 Uri *base = GetBaseInherited ();
781 Uri *current = GetSourceName ();
782 Uri *result = NULL;
783 const char *pathsep;
784 char *base_path;
786 //printf ("PlaylistEntry::GetFullSourceName (), base: %s, current: %s\n", base ? base->ToString () : "NULL", current ? current->ToString () : "NULL");
788 if (current == NULL) {
789 return NULL;
790 } else if (current->GetHost () != NULL) {
791 //printf (" current host (%s) is something, scheme: %s\n", current->GetHost (), current->scheme);
792 result = current;
793 } else if (base != NULL) {
794 result = new Uri ();
795 result->scheme = g_strdup (base->GetScheme());
796 result->user = g_strdup (base->GetUser());
797 result->passwd = g_strdup (base->GetPasswd());
798 result->host = g_strdup (base->GetHost());
799 result->port = base->GetPort();
800 // we ignore the params, query and fragment values.
801 if (current->GetPath() != NULL && current->GetPath() [0] == '/') {
802 //printf (" current path is relative to root dir on host\n");
803 result->path = g_strdup (current->GetPath());
804 } else if (base->GetPath() == NULL) {
805 //printf (" base path is root dir on host\n");
806 result->path = g_strdup (current->GetPath());
807 } else {
808 pathsep = strrchr (base->GetPath(), '/');
809 if (pathsep != NULL) {
810 if ((size_t) (pathsep - base->GetPath() + 1) == strlen (base->GetPath())) {
811 //printf (" last character of base path (%s) is /\n", base->path);
812 result->path = g_strjoin (NULL, base->GetPath(), current->GetPath(), NULL);
813 } else {
814 //printf (" base path (%s) does not end with /, only copy path up to the last /\n", base->path);
815 base_path = g_strndup (base->GetPath(), pathsep - base->GetPath() + 1);
816 result->path = g_strjoin (NULL, base_path, current->GetPath(), NULL);
817 g_free (base_path);
819 } else {
820 //printf (" base path (%s) does not contain a /\n", base->path);
821 result->path = g_strjoin (NULL, base->GetPath(), "/", current->GetPath(), NULL);
824 } else {
825 //printf (" there's no base\n");
826 result = current;
829 full_source_name = result->ToString ();
831 //printf (" result: %s\n", full_source_name);
833 if (result != base && result != current)
834 delete result;
836 return full_source_name;
839 void
840 PlaylistEntry::Open ()
842 LOG_PLAYLIST ("PlaylistEntry::Open (), media = %p, FullSourceName = %s\n", media, GetFullSourceName ());
844 if (!media) {
845 g_return_if_fail (GetFullSourceName () != NULL);
846 InitializeWithUri (GetFullSourceName ());
847 } else if (opened) {
848 OpenMediaPlayer ();
849 } else {
850 media->OpenAsync ();
854 void
855 PlaylistEntry::Play ()
857 MediaPlayer *mplayer = GetMediaPlayer ();
858 PlaylistRoot *root = GetRoot ();
860 LOG_PLAYLIST ("PlaylistEntry::Play (), play_when_available: %s, media: %p, source name: %s\n", play_when_available ? "true" : "false", media, source_name ? source_name->ToString () : "NULL");
862 g_return_if_fail (media != NULL);
863 g_return_if_fail (mplayer != NULL);
864 g_return_if_fail (root != NULL);
866 mplayer->Play ();
868 root->Emit (PlaylistRoot::PlayEvent);
871 void
872 PlaylistEntry::Pause ()
874 MediaPlayer *mplayer = GetMediaPlayer ();
875 PlaylistRoot *root = GetRoot ();
877 LOG_PLAYLIST ("PlaylistEntry::Pause ()\n");
879 g_return_if_fail (media != NULL);
880 g_return_if_fail (mplayer != NULL);
881 g_return_if_fail (root != NULL);
883 play_when_available = false;
884 media->PauseAsync ();
885 mplayer->Pause ();
887 root->Emit (PlaylistRoot::PauseEvent);
890 void
891 PlaylistEntry::Stop ()
893 LOG_PLAYLIST ("PlaylistEntry::Stop ()\n");
895 play_when_available = false;
896 if (media != NULL)
897 media->StopAsync ();
900 Media *
901 PlaylistEntry::GetMedia ()
903 return media;
906 bool
907 PlaylistEntry::IsSingleFile ()
909 return parent ? parent->IsSingleFile () : false;
912 PlaylistRoot *
913 PlaylistEntry::GetRoot ()
915 Playlist *pl;
917 if (IsDisposed ())
918 return NULL;
920 if (parent == NULL) {
921 g_return_val_if_fail (GetObjectType () == Type::PLAYLISTROOT, NULL);
922 return (PlaylistRoot *) this;
925 pl = parent;
927 while (pl->parent != NULL)
928 pl = pl->parent;
930 g_return_val_if_fail (pl->GetObjectType () == Type::PLAYLISTROOT, NULL);
932 return (PlaylistRoot *) pl;
936 * Playlist
939 Playlist::Playlist (Playlist *parent, IMediaSource *source)
940 : PlaylistEntry (Type::PLAYLIST, parent)
942 is_single_file = false;
943 waiting = false;
944 opened = false;
945 Init ();
946 this->source = source;
947 this->source->ref ();
950 Playlist::Playlist (Type::Kind kind)
951 : PlaylistEntry (kind)
953 LOG_PLAYLIST ("Playlist::Playlist ()\n");
954 is_single_file = true;
955 Init ();
957 AddEntry (new PlaylistEntry (this));
960 void
961 Playlist::Init ()
963 LOG_PLAYLIST ("Playlist::Init ()\n");
965 entries = new List ();
966 current_node = NULL;
967 source = NULL;
970 void
971 Playlist::Dispose ()
973 PlaylistNode *node;
974 PlaylistEntry *entry;
976 LOG_PLAYLIST ("Playlist::Dispose () id: %i\n", GET_OBJ_ID (this));
978 current_node = NULL;
980 if (entries != NULL) {
981 node = (PlaylistNode *) entries->First ();
982 while (node != NULL) {
983 entry = node->GetEntry ();
984 if (entry != NULL)
985 entry->Dispose ();
986 node = (PlaylistNode *) node->next;
988 delete entries;
989 entries = NULL;
992 if (source) {
993 source->unref ();
994 source = NULL;
997 PlaylistEntry::Dispose ();
1001 bool
1002 Playlist::IsCurrentEntryLastEntry ()
1004 PlaylistEntry *entry;
1005 Playlist *pl;
1007 if (entries->Last () == NULL)
1008 return false;
1010 if (current_node != entries->Last ())
1011 return false;
1013 entry = GetCurrentEntry ();
1015 if (!entry->IsPlaylist ())
1016 return true;
1018 pl = (Playlist *) entry;
1020 return pl->IsCurrentEntryLastEntry ();
1023 void
1024 Playlist::Open ()
1026 PlaylistEntry *current_entry;
1028 LOG_PLAYLIST ("Playlist::Open ()\n");
1030 current_node = (PlaylistNode *) entries->First ();
1032 current_entry = GetCurrentEntry ();
1034 while (current_entry && current_entry->HasDuration () && current_entry->GetDuration ()->HasTimeSpan() &&
1035 current_entry->GetDuration ()->GetTimeSpan () == 0) {
1036 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry->GetSourceName ()->ToString ());
1037 current_node = (PlaylistNode *) current_node->next;
1038 current_entry = GetCurrentEntry ();
1041 if (current_entry)
1042 current_entry->Open ();
1044 opened = true;
1046 LOG_PLAYLIST ("Playlist::Open (): current node: %p, current entry: %p\n", current_entry, GetCurrentEntry ());
1049 bool
1050 Playlist::PlayNext ()
1052 PlaylistEntry *current_entry;
1053 MediaElement *element = GetElement ();
1054 PlaylistRoot *root = GetRoot ();
1056 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p\n", current_node);
1057 g_return_val_if_fail (root != NULL, false);
1059 if (!current_node)
1060 return false;
1062 SetWaiting (false);
1064 current_entry = GetCurrentEntry ();
1066 if (current_entry->HasDuration() && current_entry->GetDuration()->IsForever ()) {
1067 element->SetPlayRequested ();
1068 current_entry->Play ();
1069 return true;
1072 if (current_entry->IsPlaylist ()) {
1073 Playlist *current_playlist = (Playlist *) current_entry;
1074 if (current_playlist->PlayNext ())
1075 return true;
1078 if (current_node->next) {
1079 current_node = (PlaylistNode *) current_node->next;
1081 current_entry = GetCurrentEntry ();
1082 if (current_entry) {
1083 LOG_PLAYLIST ("Playlist::PlayNext () playing entry: %p %s\n", current_entry, current_entry->GetFullSourceName ());
1084 element->SetPlayRequested ();
1085 root->Emit (PlaylistRoot::EntryChangedEvent);
1086 current_entry->Open ();
1087 return true;
1091 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p, nothing to play (is root: %i)\n", current_node, GetObjectType () == Type::PLAYLISTROOT);
1093 if (GetObjectType () == Type::PLAYLISTROOT)
1094 root->Emit (PlaylistRoot::MediaEndedEvent);
1096 return false;
1099 void
1100 Playlist::OnEntryEnded ()
1102 LOG_PLAYLIST ("Playlist::OnEntryEnded ()\n");
1103 PlayNext ();
1106 void
1107 Playlist::OnEntryFailed (ErrorEventArgs *args)
1109 bool fatal = true;
1110 PlaylistRoot *root = GetRoot ();
1112 LOG_PLAYLIST ("Playlist::OnEntryFailed () extended_code: %i is_single_file: %i\n", args ? args->extended_code : 0, is_single_file);
1114 g_return_if_fail (root != NULL);
1116 // media or playlist 404: fatal
1117 // invalid playlist (playlist parsing failed): fatal
1118 // invalid media (gif, swf): play next
1119 if (args == NULL) {
1120 fatal = true;
1121 } else {
1122 // check if we're in a playlist
1123 if (GetMedia () != NULL && GetMedia ()->GetDemuxer () != NULL && GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER) {
1124 // we're a playlist
1125 if (args->extended_code == MEDIA_UNKNOWN_CODEC) {
1126 fatal = false;
1127 } else {
1128 fatal = true;
1130 } else {
1131 // we're not a playlist
1132 fatal = true;
1136 if (fatal) {
1137 if (args)
1138 args->ref ();
1139 root->Emit (PlaylistRoot::MediaErrorEvent, args);
1140 } else {
1141 root->PlayNext ();
1145 void
1146 Playlist::Seek (guint64 pts)
1148 PlaylistEntry *current_entry;
1150 LOG_PLAYLIST ("Playlist::Seek (%llu)\n", pts);
1152 current_entry = GetCurrentEntry ();
1154 g_return_if_fail (current_entry != NULL);
1156 current_entry->Seek (pts);
1159 void
1160 Playlist::Play ()
1162 PlaylistEntry *current_entry;
1164 LOG_PLAYLIST ("Playlist::Play ()\n");
1166 current_entry = GetCurrentEntry ();
1168 g_return_if_fail (current_entry != NULL);
1170 if (current_entry && current_entry->HasDuration () && current_entry->GetDuration () == 0) {
1171 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry->GetSourceName ()->ToString ());
1172 OnEntryEnded ();
1173 } else {
1174 if (current_entry)
1175 current_entry->Play ();
1179 void
1180 Playlist::Pause ()
1182 PlaylistEntry *current_entry;
1184 LOG_PLAYLIST ("Playlist::Pause ()\n");
1186 current_entry = GetCurrentEntry ();
1188 g_return_if_fail (current_entry != NULL);
1190 current_entry->Pause ();
1193 void
1194 Playlist::Stop ()
1196 PlaylistNode *node;
1198 LOG_PLAYLIST ("Playlist::Stop ()\n");
1200 node = (PlaylistNode *) entries->First ();
1201 current_node = node; // reset to first node
1202 while (node != NULL) {
1203 node->GetEntry ()->Stop ();
1204 node = (PlaylistNode *) node->next;
1208 void
1209 Playlist::PopulateMediaAttributes ()
1211 PlaylistEntry *current_entry = GetCurrentEntry ();
1213 LOG_PLAYLIST ("Playlist::PopulateMediaAttributes ()\n");
1215 if (!current_entry)
1216 return;
1218 current_entry->PopulateMediaAttributes ();
1221 void
1222 Playlist::AddEntry (PlaylistEntry *entry)
1224 PlaylistNode *node;
1226 LOG_PLAYLIST ("Playlist::AddEntry (%p) Count: %i\n", entry, entries->Length ());
1228 node = new PlaylistNode (entry);
1229 entries->Append (node);
1230 entry->unref ();
1232 if (entries->Length () == 1) {
1233 g_return_if_fail (current_node == NULL);
1234 current_node = node;
1238 bool
1239 Playlist::ReplaceCurrentEntry (Playlist *pl)
1241 bool result;
1243 PlaylistEntry *current_entry = GetCurrentEntry ();
1245 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntry (%p)\n", pl);
1247 // check for too nested playlist
1248 int counter = 0;
1249 PlaylistEntry *e = this;
1250 while (e != NULL && e->IsPlaylist ()) {
1251 if (e->GetObjectType () != Type::PLAYLISTROOT && e->GetMedia () != NULL && e->GetMedia ()->GetDemuxer () != NULL && e->GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER)
1252 counter++;
1253 e = e->GetParent ();
1255 if (counter > 5) {
1256 ErrorEventArgs *args = new ErrorEventArgs (MediaError, 4001, "AG_E_NETWORK_ERROR");
1257 OnEntryFailed (args);
1258 args->unref ();
1259 return false;
1263 if (current_entry->IsPlaylist ()) {
1264 result = ((Playlist *) current_entry)->ReplaceCurrentEntry (pl);
1265 } else {
1266 PlaylistNode *pln = new PlaylistNode (pl);
1267 pl->MergeWith (current_entry);
1268 entries->InsertBefore (pln, current_node);
1269 entries->Remove (current_node);
1270 pl->SetParent (this);
1271 current_node = pln;
1272 result = true;
1275 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntrY (%p) [DONE]\n", pl);
1277 return result;
1280 void
1281 Playlist::MergeWith (PlaylistEntry *entry)
1283 LOG_PLAYLIST ("Playlist::MergeWith (%p)\n", entry);
1285 SetBase (entry->GetBase () ? new Uri (*entry->GetBase ()) : NULL);
1286 SetTitle (entry->GetTitle ());
1287 SetAuthor (entry->GetAuthor ());
1288 SetAbstract (entry->GetAbstract ());
1289 SetCopyright (entry->GetCopyright ());
1291 SetSourceName (entry->GetSourceName () ? new Uri (*entry->GetSourceName ()) : NULL);
1292 if (entry->HasDuration ())
1293 SetDuration (entry->GetDuration ());
1294 Initialize (entry->GetMedia ());
1295 entry->ClearMedia ();
1298 PlaylistEntry *
1299 Playlist::GetCurrentPlaylistEntry ()
1301 PlaylistEntry *result = NULL;
1303 if (current_node)
1304 result = current_node->GetEntry () ->GetCurrentPlaylistEntry ();
1306 return result;
1309 * PlaylistRoot
1312 PlaylistRoot::PlaylistRoot (MediaElement *element)
1313 : Playlist (Type::PLAYLISTROOT)
1315 this->element = element;
1317 seek_pts = 0;
1318 mplayer = element->GetMediaPlayer ();
1319 mplayer->AddHandler (MediaPlayer::MediaEndedEvent, MediaEndedCallback, this);
1320 mplayer->AddHandler (MediaPlayer::BufferUnderflowEvent, BufferUnderflowCallback, this);
1321 mplayer->ref ();
1324 void
1325 PlaylistRoot::Dispose ()
1327 if (mplayer != NULL) {
1328 mplayer->RemoveAllHandlers (this);
1329 mplayer->unref ();
1330 mplayer = NULL;
1333 Playlist::Dispose ();
1336 bool
1337 PlaylistRoot::IsSingleFile ()
1339 PlaylistEntry *entry;
1341 if (GetCount () != 1)
1342 return false;
1344 entry = GetCurrentEntry ();
1345 if (entry == NULL)
1346 return false;
1348 if (entry->GetObjectType () == Type::PLAYLISTENTRY)
1349 return true;
1351 return entry->IsSingleFile ();
1354 #if DEBUG
1355 void
1356 PlaylistEntry::DumpInternal (int tabs)
1358 printf ("%*s%s %i\n", tabs, "", GetTypeName (), GET_OBJ_ID (this));
1359 tabs++;
1360 printf ("%*sParent: %p %s\n", tabs, "", parent, parent ? parent->GetTypeName () : NULL);
1361 printf ("%*sFullSourceName: %s\n", tabs, "", GetFullSourceName ());
1362 printf ("%*sDuration: %s %.2f seconds\n", tabs, "", HasDuration () ? "yes" : "no", HasDuration () ? GetDuration ()->ToSecondsFloat () : 0.0);
1363 printf ("%*sMedia: %i %s\n", tabs, "", GET_OBJ_ID (media), media ? "" : "(null)");
1364 if (media) {
1365 printf ("%*sUri: %s\n", tabs, "", media->GetUri ());
1366 printf ("%*sDemuxer: %i %s\n", tabs, "", GET_OBJ_ID (media->GetDemuxer ()), media->GetDemuxer () ? media->GetDemuxer ()->GetTypeName () : "N/A");
1367 printf ("%*sSource: %i %s\n", tabs, "", GET_OBJ_ID (media->GetSource ()), media->GetSource () ? media->GetSource ()->GetTypeName () : "N/A");
1372 void
1373 Playlist::DumpInternal (int tabs)
1375 PlaylistNode *node;
1377 PlaylistEntry::DumpInternal (tabs);
1378 printf ("%*s %i entries:\n", tabs, "", entries->Length ());
1379 node = (PlaylistNode *) entries->First ();
1380 while (node != NULL) {
1381 if (node == current_node)
1382 printf ("*%*s * CURRENT NODE *\n", tabs, "");
1383 node->GetEntry ()->DumpInternal (tabs + 2);
1384 node = (PlaylistNode *) node->next;
1387 void
1388 PlaylistRoot::Dump ()
1390 printf ("\n\nDUMP OF PLAYLIST\n\n");
1391 DumpInternal (0);
1392 printf ("\n\nDUMP OF PLAYLIST DONE\n\n");
1394 #endif
1396 void
1397 PlaylistRoot::SeekCallback (EventObject *obj)
1399 PlaylistRoot *playlist = (PlaylistRoot *) obj;
1401 LOG_PLAYLIST ("Playlist::SeekCallback () pts: %" G_GUINT64_FORMAT "\n", playlist->seek_pts);
1403 if (playlist->seek_pts != G_MAXUINT64) {
1404 guint64 pts = playlist->seek_pts;
1405 playlist->seek_pts = G_MAXUINT64;
1406 playlist->Seek (pts);
1410 void
1411 PlaylistRoot::SeekAsync (guint64 pts)
1413 LOG_PLAYLIST ("Playlist::SeekAsync (%" G_GUINT64_FORMAT ")\n", pts);
1414 seek_pts = pts;
1415 AddTickCall (SeekCallback);
1418 void
1419 PlaylistRoot::PlayCallback (EventObject *obj)
1421 LOG_PLAYLIST ("Playlist::PlayCallback ()\n");
1422 ((PlaylistRoot *) obj)->Play ();
1425 void
1426 PlaylistRoot::PlayAsync ()
1428 LOG_PLAYLIST ("Playlist::PlayAsync ()\n");
1429 AddTickCall (PlayCallback);
1432 void
1433 PlaylistRoot::PauseCallback (EventObject *obj)
1435 LOG_PLAYLIST ("Playlist::PauseCallback ()\n");
1436 ((PlaylistRoot *) obj)->Pause ();
1439 void
1440 PlaylistRoot::PauseAsync ()
1442 LOG_PLAYLIST ("Playlist::PauseAsync ()\n");
1443 AddTickCall (PauseCallback);
1446 void
1447 PlaylistRoot::OpenCallback (EventObject *obj)
1449 LOG_PLAYLIST ("Playlist::OpenCallback ()\n");
1450 ((PlaylistRoot *) obj)->Open ();
1453 void
1454 PlaylistRoot::OpenAsync ()
1456 LOG_PLAYLIST ("Playlist::OpenAsync ()\n");
1457 AddTickCall (OpenCallback);
1460 void
1461 PlaylistRoot::StopCallback (EventObject *obj)
1463 LOG_PLAYLIST ("Playlist::StopCallback ()\n");
1464 ((PlaylistRoot *) obj)->Stop ();
1467 void
1468 PlaylistRoot::StopAsync ()
1470 LOG_PLAYLIST ("Playlist::StopAsync ()\n");
1471 AddTickCall (StopCallback);
1474 void
1475 PlaylistRoot::Stop ()
1477 MediaPlayer *mplayer;
1479 LOG_PLAYLIST ("PlaylistRoot::Stop ()\n");
1481 mplayer = GetMediaPlayer ();
1483 Playlist::Stop ();
1484 if (mplayer != NULL)
1485 mplayer->Stop ();
1487 OpenAsync ();
1488 Emit (StopEvent); // we emit the event after enqueuing the Open request, do avoid funky side-effects of event emission.
1491 void
1492 PlaylistRoot::EmitBufferUnderflowEvent (EventObject *obj)
1494 PlaylistRoot *root = (PlaylistRoot *) obj;
1495 root->Emit (BufferUnderflowEvent);
1498 MediaPlayer *
1499 PlaylistRoot::GetMediaPlayer ()
1501 return mplayer;
1504 Media *
1505 PlaylistRoot::GetCurrentMedia ()
1507 PlaylistEntry *entry = GetCurrentEntry ();
1509 if (entry == NULL)
1510 return NULL;
1512 return entry->GetMedia ();
1515 MediaElement *
1516 PlaylistRoot::GetElement ()
1518 return element;
1521 void
1522 PlaylistRoot::MediaEndedHandler (MediaPlayer *mplayer, EventArgs *args)
1524 LOG_PLAYLIST ("PlaylistRoot::MediaEndedHandler (%p, %p)\n", mplayer, args);
1526 OnEntryEnded ();
1528 // Emit (MediaEndedEvent, args);
1531 void
1532 PlaylistRoot::BufferUnderflowHandler (MediaPlayer *mplayer, EventArgs *args)
1534 LOG_PLAYLIST ("PlaylistRoot::BufferUnderflowHandler (%p, %p)\n", mplayer, args);
1536 if (Surface::InMainThread ()) {
1537 EmitBufferUnderflowEvent (this);
1538 } else {
1539 AddTickCall (EmitBufferUnderflowEvent);
1544 * PlaylistParser
1547 PlaylistParser::PlaylistParser (PlaylistRoot *root, IMediaSource *source)
1549 this->root = root;
1550 this->source = source;
1551 this->internal = NULL;
1552 this->kind_stack = NULL;
1553 this->playlist = NULL;
1554 this->current_entry = NULL;
1555 this->current_text = NULL;
1556 this->error_args = NULL;
1559 void
1560 PlaylistParser::SetSource (IMediaSource *new_source)
1562 if (source)
1563 source->unref ();
1564 source = new_source;
1565 if (source)
1566 source->ref ();
1569 void
1570 PlaylistParser::Setup (XmlType type)
1572 playlist = NULL;
1573 current_entry = NULL;
1574 current_text = NULL;
1576 was_playlist = false;
1578 internal = new PlaylistParserInternal ();
1579 kind_stack = new List ();
1580 PushCurrentKind (PlaylistKind::Root);
1582 if (type == XML_TYPE_ASX3) {
1583 XML_SetUserData (internal->parser, this);
1584 XML_SetElementHandler (internal->parser, on_asx_start_element, on_asx_end_element);
1585 XML_SetCharacterDataHandler (internal->parser, on_asx_text);
1590 void
1591 PlaylistParser::Cleanup ()
1593 if (kind_stack) {
1594 kind_stack->Clear (true);
1595 delete kind_stack;
1596 kind_stack = NULL;
1598 delete internal;
1599 internal = NULL;
1600 if (playlist) {
1601 playlist->unref ();
1602 playlist = NULL;
1604 if (error_args) {
1605 error_args->unref ();
1606 error_args = NULL;
1610 PlaylistParser::~PlaylistParser ()
1612 Cleanup ();
1615 static bool
1616 str_match (const char *candidate, const char *tag)
1618 return g_ascii_strcasecmp (candidate, tag) == 0;
1621 void
1622 PlaylistParser::on_asx_start_element (gpointer user_data, const char *name, const char **attrs)
1624 ((PlaylistParser *) user_data)->OnASXStartElement (name, attrs);
1627 void
1628 PlaylistParser::on_asx_end_element (gpointer user_data, const char *name)
1630 ((PlaylistParser *) user_data)->OnASXEndElement (name);
1633 void
1634 PlaylistParser::on_asx_text (gpointer user_data, const char *data, int len)
1636 ((PlaylistParser *) user_data)->OnASXText (data, len);
1639 static bool
1640 is_all_whitespace (const char *str)
1642 if (str == NULL)
1643 return true;
1645 for (int i = 0; str [i] != 0; i++) {
1646 switch (str [i]) {
1647 case 10:
1648 case 13:
1649 case ' ':
1650 case '\t':
1651 break;
1652 default:
1653 return false;
1656 return true;
1660 // To make matters more interesting, the format of the VALUE attribute in the STARTTIME tag isn't
1661 // exactly the same as xaml's or javascript's TimeSpan format.
1663 // The time index, in hours, minutes, seconds, and hundredths of seconds.
1664 // [[hh]:mm]:ss.fract
1666 // The parser seems to stop if it finds a second dot, returnning whatever it had parsed
1667 // up till then
1669 // At most 4 digits of fract is read, the rest is ignored (even if it's not numbers).
1671 static bool
1672 parse_int (const char **pp, const char *end, int *result)
1674 const char *p = *pp;
1675 int res = 0;
1676 bool success = false;
1678 while (p <= end && g_ascii_isdigit (*p)) {
1679 res = res * 10 + *p - '0';
1680 p++;
1683 success = *pp != p;
1685 *pp = p;
1686 *result = res;
1688 return success;
1691 static bool
1692 duration_from_asx_str (PlaylistParser *parser, const char *str, Duration **res)
1694 const char *end = str + strlen (str);
1695 const char *p;
1697 int values [] = {0, 0, 0};
1698 int counter = 0;
1699 int hh = 0, mm = 0, ss = 0;
1700 int milliseconds = 0;
1701 int digits = 2;
1703 p = str;
1705 if (!g_ascii_isdigit (*p)) {
1706 parser->ParsingError (new ErrorEventArgs (MediaError, 2210, "AG_E_INVALID_ARGUMENT"));
1707 return false;
1710 for (int i = 0; i < 3; i++) {
1711 if (!parse_int (&p, end, &values [i])) {
1712 parser->ParsingError (new ErrorEventArgs (MediaError, 2210, "AG_E_INVALID_ARGUMENT"));
1713 return false;
1715 counter++;
1716 if (*p != ':')
1717 break;
1718 p++;
1721 if (*p == '.') {
1722 p++;
1723 while (digits >= 0 && g_ascii_isdigit (*p)) {
1724 milliseconds += pow (10.0f, digits) * (*p - '0');
1725 p++;
1726 digits--;
1728 if (counter == 3 && *p != 0 && !g_ascii_isdigit (*p)) {
1729 parser->ParsingError (new ErrorEventArgs (MediaError, 2210, "AG_E_INVALID_ARGUMENT"));
1730 return false;
1734 switch (counter) {
1735 case 1:
1736 ss = values [0];
1737 break;
1738 case 2:
1739 ss = values [1];
1740 mm = values [0];
1741 break;
1742 case 3:
1743 ss = values [2];
1744 mm = values [1];
1745 hh = values [0];
1746 break;
1747 default:
1748 parser->ParsingError (new ErrorEventArgs (MediaError, 2210, "AG_E_INVALID_ARGUMENT"));
1749 return false;
1752 gint64 ms = ((hh * 3600) + (mm * 60) + ss) * 1000 + milliseconds;
1753 TimeSpan result = TimeSpan_FromPts (MilliSeconds_ToPts (ms));
1754 Duration *duration = new Duration (result);
1756 *res = duration;
1758 return true;
1761 void
1762 PlaylistParser::OnASXStartElement (const char *name, const char **attrs)
1764 PlaylistKind::Kind kind = StringToKind (name);
1765 Uri *uri = NULL;
1766 bool failed;
1768 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), kind = %d\n", name, attrs, kind);
1770 g_free (current_text);
1771 current_text = NULL;
1773 PushCurrentKind (kind);
1775 switch (kind) {
1776 case PlaylistKind::Abstract:
1777 if (attrs != NULL && attrs [0] != NULL)
1778 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1779 break;
1780 case PlaylistKind::Asx:
1781 // Here the kind stack should be: Root+Asx
1782 if (kind_stack->Length () != 2 || !AssertParentKind (PlaylistKind::Root)) {
1783 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
1784 return;
1787 playlist = new Playlist (root, source);
1789 for (int i = 0; attrs [i] != NULL; i += 2) {
1790 if (str_match (attrs [i], "VERSION")) {
1791 if (str_match (attrs [i+1], "3")) {
1792 playlist_version = 3;
1793 } else if (str_match (attrs [i+1], "3.0")) {
1794 playlist_version = 3;
1795 } else {
1796 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
1798 } else if (str_match (attrs [i], "BANNERBAR")) {
1799 ParsingError (new ErrorEventArgs (MediaError, 3007, "Unsupported ASX attribute"));
1800 } else if (str_match (attrs [i], "PREVIEWMODE")) {
1801 ParsingError (new ErrorEventArgs (MediaError, 3007, "Unsupported ASX attribute"));
1802 } else {
1803 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1806 break;
1807 case PlaylistKind::Author:
1808 if (attrs != NULL && attrs [0] != NULL)
1809 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1810 break;
1811 case PlaylistKind::Banner:
1812 if (attrs != NULL && attrs [0] != NULL)
1813 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1814 break;
1815 case PlaylistKind::Base:
1816 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
1817 break;
1818 for (int i = 0; attrs [i] != NULL; i += 2) {
1819 if (str_match (attrs [i], "HREF")) {
1820 // TODO: What do we do with this value?
1821 if (GetCurrentContent () != NULL) {
1822 failed = false;
1823 uri = new Uri ();
1824 if (!uri->Parse (attrs [i+1], true)) {
1825 failed = true;
1826 } else if (uri->GetScheme() == NULL) {
1827 failed = true;
1828 } else if (uri->IsScheme ("http") &&
1829 uri->IsScheme ("https") &&
1830 uri->IsScheme ("mms") &&
1831 uri->IsScheme ("rtsp") &&
1832 uri->IsScheme ("rstpt")) {
1833 failed = true;
1836 if (!failed) {
1837 GetCurrentContent ()->SetBase (uri);
1838 } else {
1839 delete uri;
1840 ParsingError (new ErrorEventArgs (MediaError, 4001, "AG_E_NETWORK_ERROR"));
1842 uri = NULL;
1844 } else {
1845 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1846 break;
1849 break;
1850 case PlaylistKind::Copyright:
1851 if (attrs != NULL && attrs [0] != NULL)
1852 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1853 break;
1854 case PlaylistKind::Duration: {
1855 Duration *dur;
1856 for (int i = 0; attrs [i] != NULL; i += 2) {
1857 if (str_match (attrs [i], "VALUE")) {
1858 if (duration_from_asx_str (this, attrs [i+1], &dur)) {
1859 if (GetCurrentEntry () != NULL && GetParentKind () != PlaylistKind::Ref) {
1860 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name, attrs, TimeSpan_ToSecondsFloat (dur->GetTimeSpan()));
1861 GetCurrentEntry ()->SetDuration (dur);
1864 } else {
1865 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1866 break;
1869 break;
1871 case PlaylistKind::Entry: {
1872 bool client_skip = true;
1873 for (int i = 0; attrs [i] != NULL; i += 2) {
1874 if (str_match (attrs [i], "CLIENTSKIP")) {
1875 // TODO: What do we do with this value?
1876 if (str_match (attrs [i+1], "YES")) {
1877 client_skip = true;
1878 } else if (str_match (attrs [i+1], "NO")) {
1879 client_skip = false;
1880 } else {
1881 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
1882 break;
1884 } else if (str_match (attrs [i], "SKIPIFREF")) {
1885 ParsingError (new ErrorEventArgs (MediaError, 3007, "Unsupported ASX attribute"));
1886 break;
1887 } else {
1888 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1889 break;
1892 PlaylistEntry *entry = new PlaylistEntry (playlist);
1893 entry->SetClientSkip (client_skip);
1894 playlist->AddEntry (entry);
1895 current_entry = entry;
1896 break;
1898 case PlaylistKind::EntryRef: {
1899 char *href = NULL;
1900 for (int i = 0; attrs [i] != NULL; i += 2) {
1901 if (str_match (attrs [i], "HREF")) {
1902 if (href == NULL)
1903 href = g_strdup (attrs [i+1]);
1904 // Docs says this attribute isn't unsupported, but an error is emitted.
1905 //} else if (str_match (attrs [i], "CLIENTBIND")) {
1906 // // TODO: What do we do with this value?
1907 } else {
1908 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1909 break;
1913 if (href) {
1914 uri = new Uri ();
1915 if (!uri->Parse (href)) {
1916 delete uri;
1917 uri = NULL;
1918 ParsingError (new ErrorEventArgs (MediaError, 1001, "AG_E_UNKNOWN_ERROR"));
1922 PlaylistEntry *entry = new PlaylistEntry (playlist);
1923 if (uri)
1924 entry->SetSourceName (uri);
1925 uri = NULL;
1926 playlist->AddEntry (entry);
1927 current_entry = entry;
1928 break;
1930 case PlaylistKind::LogUrl:
1931 if (attrs != NULL && attrs [0] != NULL)
1932 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1933 break;
1934 case PlaylistKind::MoreInfo:
1935 for (int i = 0; attrs [i] != NULL; i += 2) {
1936 if (str_match (attrs [i], "HREF")) {
1937 if (GetCurrentEntry () != NULL)
1938 GetCurrentEntry ()->SetInfoURL (attrs [i+1]);
1939 } else if (str_match (attrs [i], "TARGET")) {
1940 if (GetCurrentEntry () != NULL)
1941 GetCurrentEntry ()->SetInfoTarget (attrs [i+1]);
1942 } else {
1943 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1944 break;
1947 break;
1948 case PlaylistKind::StartTime: {
1949 Duration *dur;
1950 for (int i = 0; attrs [i] != NULL; i += 2) {
1951 if (str_match (attrs [i], "VALUE")) {
1952 if (duration_from_asx_str (this, attrs [i+1], &dur) && dur->HasTimeSpan ()) {
1953 if (GetCurrentEntry () != NULL && GetParentKind () != PlaylistKind::Ref) {
1954 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name, attrs, TimeSpan_ToSecondsFloat (dur->GetTimeSpan()));
1955 GetCurrentEntry ()->SetStartTime (dur->GetTimeSpan ());
1958 } else {
1959 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1960 break;
1964 break;
1966 case PlaylistKind::Ref: {
1967 for (int i = 0; attrs [i] != NULL; i += 2) {
1968 if (str_match (attrs [i], "HREF")) {
1969 if (GetCurrentEntry () != NULL && GetCurrentEntry ()->GetSourceName () == NULL) {
1970 uri = new Uri ();
1971 if (uri->Parse (attrs [i+1])) {
1972 GetCurrentEntry ()->SetSourceName (uri);
1973 } else {
1974 delete uri;
1975 ParsingError (new ErrorEventArgs (MediaError, 1001, "AG_E_UNKNOWN_ERROR"));
1977 uri = NULL;
1979 } else {
1980 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1981 break;
1984 break;
1986 case PlaylistKind::Param: {
1987 const char *name = NULL;
1988 const char *value = NULL;
1990 for (int i = 0; attrs [i] != NULL; i+= 2) {
1991 if (str_match (attrs [i], "name")) {
1992 name = attrs [i + 1];
1993 } else if (str_match (attrs [i], "value")) {
1994 value = attrs [i + 1];
1995 } else {
1996 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
1997 break;
2000 if (value != NULL && value [0] != 0 && name != NULL && name [0] != 0) {
2001 PlaylistEntry *entry = GetCurrentEntry ();
2002 if (entry == NULL)
2003 entry = playlist;
2004 if (entry == NULL)
2005 entry = root;
2006 entry->AddParams (name, value);
2007 } else {
2008 // TODO: add test
2010 break;
2012 case PlaylistKind::Title:
2013 if (attrs != NULL && attrs [0] != NULL)
2014 ParsingError (new ErrorEventArgs (MediaError, 3005, "Invalid ASX attribute"));
2015 break;
2016 case PlaylistKind::StartMarker:
2017 case PlaylistKind::EndMarker:
2018 case PlaylistKind::Repeat:
2019 case PlaylistKind::Event:
2020 ParsingError (new ErrorEventArgs (MediaError, 3006, "Unsupported ASX element"));
2021 break;
2022 case PlaylistKind::Root:
2023 case PlaylistKind::Unknown:
2024 default:
2025 LOG_PLAYLIST ("PlaylistParser::OnStartElement ('%s', %p): Unknown kind: %d\n", name, attrs, kind);
2026 ParsingError (new ErrorEventArgs (MediaError, 3004, "Invalid ASX element"));
2027 break;
2031 void
2032 PlaylistParser::OnASXEndElement (const char *name)
2034 PlaylistKind::Kind kind = GetCurrentKind ();
2035 Duration *dur;
2037 LOG_PLAYLIST ("PlaylistParser::OnEndElement (%s), GetCurrentKind (): %d, GetCurrentKind () to string: %s\n", name, kind, KindToString (kind));
2039 switch (kind) {
2040 case PlaylistKind::Abstract:
2041 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2042 break;
2043 if (GetCurrentContent () != NULL)
2044 GetCurrentContent ()->SetAbstract (current_text);
2045 break;
2046 case PlaylistKind::Author:
2047 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2048 break;
2049 if (GetCurrentContent () != NULL)
2050 GetCurrentContent ()->SetAuthor (current_text);
2051 break;
2052 case PlaylistKind::Base:
2053 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2054 break;
2055 break;
2056 case PlaylistKind::Copyright:
2057 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2058 break;
2059 if (GetCurrentContent () != NULL)
2060 GetCurrentContent ()->SetCopyright (current_text);
2061 break;
2062 case PlaylistKind::Duration:
2063 if (!AssertParentKind (PlaylistKind::Entry | PlaylistKind::Ref))
2064 break;
2065 if (current_text == NULL)
2066 break;
2067 duration_from_asx_str (this, current_text, &dur);
2068 if (GetCurrentEntry () != NULL)
2069 GetCurrentEntry ()->SetDuration (dur);
2070 break;
2071 case PlaylistKind::Entry:
2072 if (!AssertParentKind (PlaylistKind::Asx))
2073 break;
2074 if (!is_all_whitespace (current_text)) {
2075 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
2077 break;
2078 case PlaylistKind::EntryRef:
2079 if (!AssertParentKind (PlaylistKind::Asx))
2080 break;
2081 break;
2082 case PlaylistKind::StartTime:
2083 if (!AssertParentKind (PlaylistKind::Entry | PlaylistKind::Ref))
2084 break;
2085 if (!is_all_whitespace (current_text)) {
2086 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
2088 break;
2089 case PlaylistKind::Title:
2090 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2091 break;
2092 if (GetCurrentContent () != NULL)
2093 GetCurrentContent ()->SetTitle (current_text);
2094 break;
2095 case PlaylistKind::Asx:
2096 if (playlist_version == 3)
2097 was_playlist = true;
2098 if (!AssertParentKind (PlaylistKind::Root))
2099 break;
2100 break;
2101 case PlaylistKind::Ref:
2102 if (!AssertParentKind (PlaylistKind::Entry))
2103 break;
2104 if (!is_all_whitespace (current_text)) {
2105 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
2107 break;
2108 case PlaylistKind::MoreInfo:
2109 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2110 break;
2111 if (!is_all_whitespace (current_text)) {
2112 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
2114 break;
2115 case PlaylistKind::Param:
2116 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2117 break;
2118 if (!is_all_whitespace (current_text)) {
2119 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
2121 break;
2122 default:
2123 LOG_PLAYLIST ("PlaylistParser::OnEndElement ('%s'): Unknown kind %d.\n", name, kind);
2124 ParsingError (new ErrorEventArgs (MediaError, 3004, "Invalid ASX element"));
2125 break;
2128 if (current_text != NULL) {
2129 g_free (current_text);
2130 current_text = NULL;
2133 switch (GetCurrentKind ()) {
2134 case PlaylistKind::Entry:
2135 EndEntry ();
2136 break;
2137 default:
2138 break;
2140 PopCurrentKind ();
2143 void
2144 PlaylistParser::OnASXText (const char *text, int len)
2146 char *a = g_strndup (text, len);
2148 #if DEBUG
2149 char *p = g_strndup (text, len);
2150 for (int i = 0; p [i] != 0; i++)
2151 if (p [i] == 10 || p [i] == 13)
2152 p [i] = ' ';
2154 LOG_PLAYLIST ("PlaylistParser::OnText (%s, %d)\n", p, len);
2155 g_free (p);
2156 #endif
2158 if (current_text == NULL) {
2159 current_text = a;
2160 } else {
2161 char *b = g_strconcat (current_text, a, NULL);
2162 g_free (current_text);
2163 current_text = b;
2167 bool
2168 PlaylistParser::Is (IMediaSource *source, const char *asx_header)
2170 bool result = false;
2171 int asx_header_length = strlen (asx_header);
2172 unsigned char buffer [20];
2174 do {
2175 result = source->Peek ((guint8 *) buffer, asx_header_length);
2176 if (!result)
2177 goto cleanup;
2179 // skip any whitespace
2180 unsigned char c = buffer [0];
2181 switch (c) {
2182 case ' ':
2183 case '\t':
2184 case 10:
2185 case 13: {
2186 result = source->ReadAll ((guint8 *) buffer, 1);
2187 if (!result)
2188 goto cleanup;
2189 continue;
2191 case 0xef: {
2192 if (buffer [1] == 0xbb && buffer [2] == 0xbf) { // UTF-8 BOM: EF BB BF
2193 result = source->ReadAll ((guint8 *) buffer, 3);
2194 if (!result)
2195 goto cleanup;
2196 continue;
2198 // TODO: there might be other BOMs we should handle too
2199 // fall through
2201 default:
2202 result = !g_ascii_strncasecmp ((const char *) buffer, asx_header, asx_header_length);
2203 goto cleanup;
2205 } while (true);
2207 cleanup:
2209 source->Seek (0, SEEK_SET);
2211 return result;
2214 bool
2215 PlaylistParser::IsASX3 (IMediaSource *source)
2217 return Is (source, "<ASX");
2220 bool
2221 PlaylistParser::IsASX2 (IMediaSource *source)
2223 return Is (source, "[Reference]");
2226 bool
2227 PlaylistParser::ParseASX2 ()
2229 const int BUFFER_SIZE = 1024;
2230 int bytes_read;
2231 char buffer[BUFFER_SIZE];
2232 char *ref;
2233 char *mms_uri;
2234 GKeyFile *key_file;
2235 Uri *uri;
2237 playlist_version = 2;
2239 bytes_read = source->ReadSome (buffer, BUFFER_SIZE);
2240 if (bytes_read < 0) {
2241 LOG_PLAYLIST_WARN ("Could not read asx document for parsing.\n");
2242 return false;
2245 key_file = g_key_file_new ();
2246 if (!g_key_file_load_from_data (key_file, buffer, bytes_read,
2247 G_KEY_FILE_NONE, NULL)) {
2248 LOG_PLAYLIST_WARN ("Invalid asx2 document.\n");
2249 g_key_file_free (key_file);
2250 return false;
2253 ref = g_key_file_get_value (key_file, "Reference", "Ref1", NULL);
2254 if (ref == NULL) {
2255 LOG_PLAYLIST_WARN ("Could not find Ref1 entry in asx2 document.\n");
2256 g_key_file_free (key_file);
2257 return false;
2260 if (!g_str_has_prefix (ref, "http://") || !g_str_has_suffix (ref, "MSWMExt=.asf")) {
2261 LOG_PLAYLIST_WARN ("Could not find a valid uri within Ref1 entry in asx2 document.\n");
2262 g_free (ref);
2263 g_key_file_free (key_file);
2264 return false;
2267 mms_uri = g_strdup_printf ("mms://%s", strstr (ref, "http://") + strlen ("http://"));
2268 g_free (ref);
2269 g_key_file_free (key_file);
2272 playlist = new Playlist (root, source);
2274 PlaylistEntry *entry = new PlaylistEntry (playlist);
2275 uri = new Uri ();
2276 if (uri->Parse (mms_uri)) {
2277 entry->SetSourceName (uri);
2278 } else {
2279 delete uri;
2281 playlist->AddEntry (entry);
2282 current_entry = entry;
2284 return true;
2287 bool
2288 PlaylistParser::TryFixError (gint8 *current_buffer, int bytes_read)
2290 Media *media;
2292 if (XML_GetErrorCode (internal->parser) != XML_ERROR_INVALID_TOKEN)
2293 return false;
2295 int index = XML_GetErrorByteIndex (internal->parser);
2297 if (index > bytes_read)
2298 return false;
2300 LOG_PLAYLIST ("Attempting to fix invalid token error index: %d\n", index);
2302 // OK, so we are going to guess that we are in an attribute here and walk back
2303 // until we hit a control char that should be escaped.
2304 char * escape = NULL;
2305 while (index >= 0) {
2306 switch (current_buffer [index]) {
2307 case '&':
2308 escape = g_strdup ("&amp;");
2309 break;
2310 case '<':
2311 escape = g_strdup ("&lt;");
2312 break;
2313 case '>':
2314 escape = g_strdup ("&gt;");
2315 break;
2316 case '\"':
2317 break;
2319 if (escape)
2320 break;
2321 index--;
2324 if (!escape) {
2325 LOG_PLAYLIST_WARN ("Unable to find an invalid escape character to fix in ASX: %s.\n", current_buffer);
2326 g_free (escape);
2327 return false;
2330 int escape_len = strlen (escape);
2331 int new_size = source->GetSize () + escape_len - 1;
2332 int patched_size = internal->bytes_read + bytes_read + escape_len - 1;
2333 gint8 * new_buffer = (gint8 *) g_malloc (new_size);
2335 source->Seek (0, SEEK_SET);
2336 source->ReadSome (new_buffer, internal->bytes_read);
2338 memcpy (new_buffer + internal->bytes_read, current_buffer, index);
2339 memcpy (new_buffer + internal->bytes_read + index, escape, escape_len);
2340 memcpy (new_buffer + internal->bytes_read + index + escape_len, current_buffer + index + 1, bytes_read - index - 1);
2342 source->Seek (internal->bytes_read + bytes_read, SEEK_SET);
2343 source->ReadSome (new_buffer + patched_size, new_size - patched_size);
2345 media = source->GetMediaReffed ();
2347 MemorySource *reparse_source = new MemorySource (media, new_buffer, new_size);
2348 SetSource (reparse_source);
2349 reparse_source->unref ();
2351 internal->reparse = true;
2353 g_free (escape);
2355 if (media)
2356 media->unref ();
2358 return true;
2361 MediaResult
2362 PlaylistParser::Parse ()
2364 bool result;
2365 gint64 last_available_pos;
2366 gint64 size;
2368 LOG_PLAYLIST ("PlaylistParser::Parse ()\n");
2370 do {
2371 // Don't try to parse anything until we have all the data.
2372 if (internal != NULL)
2373 internal->reparse = false;
2374 size = source->GetSize ();
2375 last_available_pos = source->GetLastAvailablePosition ();
2376 if (size != -1 && last_available_pos != -1 && size != last_available_pos)
2377 return MEDIA_NOT_ENOUGH_DATA;
2379 if (this->IsASX2 (source)) {
2380 /* Parse as a asx2 mms file */
2381 Setup (XML_TYPE_NONE);
2382 result = this->ParseASX2 ();
2383 } else if (this->IsASX3 (source)) {
2384 Setup (XML_TYPE_ASX3);
2385 result = this->ParseASX3 ();
2386 } else {
2387 result = false;
2389 } while (result && internal->reparse);
2391 return result ? MEDIA_SUCCESS : MEDIA_FAIL;
2394 bool
2395 PlaylistParser::ParseASX3 ()
2397 int bytes_read;
2398 void *buffer;
2400 // asx documents don't tend to be very big, so there's no need for a big buffer
2401 const int BUFFER_SIZE = 1024;
2403 for (;;) {
2404 buffer = XML_GetBuffer(internal->parser, BUFFER_SIZE);
2405 if (buffer == NULL) {
2406 fprintf (stderr, "Could not allocate memory for asx document parsing.\n");
2407 return false;
2410 bytes_read = source->ReadSome (buffer, BUFFER_SIZE);
2411 if (bytes_read < 0) {
2412 fprintf (stderr, "Could not read asx document for parsing.\n");
2413 return false;
2416 if (!XML_ParseBuffer (internal->parser, bytes_read, bytes_read == 0)) {
2417 if (error_args != NULL)
2418 return false;
2420 switch (XML_GetErrorCode (internal->parser)) {
2421 case XML_ERROR_NO_ELEMENTS:
2422 ParsingError (new ErrorEventArgs (MediaError, 7000, "unexpected end of input"));
2423 return false;
2424 case XML_ERROR_DUPLICATE_ATTRIBUTE:
2425 ParsingError (new ErrorEventArgs (MediaError, 7031, "wfc: unique attribute spec"));
2426 return false;
2427 case XML_ERROR_INVALID_TOKEN:
2428 // save error args in case the error fixing fails (in which case we want this error, not the error the error fixing caused)
2429 error_args = new ErrorEventArgs (MediaError, 7007, "quote expected");
2430 if (TryFixError ((gint8 *) buffer, bytes_read))
2431 return true;
2432 // fall through
2433 default:
2434 char *msg = g_strdup_printf ("%s %d (%d, %d)",
2435 XML_ErrorString (XML_GetErrorCode (internal->parser)), (int) XML_GetErrorCode (internal->parser),
2436 (int) XML_GetCurrentLineNumber (internal->parser), (int) XML_GetCurrentColumnNumber (internal->parser));
2437 ParsingError (new ErrorEventArgs (MediaError, 3000, msg));
2438 g_free (msg);
2439 return false;
2443 if (bytes_read == 0)
2444 break;
2446 internal->bytes_read += bytes_read;
2449 return playlist != NULL;
2452 PlaylistEntry *
2453 PlaylistParser::GetCurrentContent ()
2455 if (current_entry != NULL)
2456 return current_entry;
2458 return playlist;
2461 PlaylistEntry *
2462 PlaylistParser::GetCurrentEntry ()
2464 return current_entry;
2467 void
2468 PlaylistParser::EndEntry ()
2470 this->current_entry = NULL;
2473 void
2474 PlaylistParser::PushCurrentKind (PlaylistKind::Kind kind)
2476 kind_stack->Append (new KindNode (kind));
2477 LOG_PLAYLIST ("PlaylistParser::Push (%d)\n", kind);
2480 void
2481 PlaylistParser::PopCurrentKind ()
2483 LOG_PLAYLIST ("PlaylistParser::PopCurrentKind (), current: %d\n", ((KindNode *)kind_stack->Last ())->kind);
2484 kind_stack->Remove (kind_stack->Last ());
2487 PlaylistKind::Kind
2488 PlaylistParser::GetCurrentKind ()
2490 KindNode *node = (KindNode *) kind_stack->Last ();
2491 return node->kind;
2494 PlaylistKind::Kind
2495 PlaylistParser::GetParentKind ()
2497 KindNode *node = (KindNode *) kind_stack->Last ()->prev;
2498 return node->kind;
2501 bool
2502 PlaylistParser::AssertParentKind (int kind)
2504 LOG_PLAYLIST ("PlaylistParser::AssertParentKind (%d), GetParentKind: %d, result: %d\n", kind, GetParentKind (), GetParentKind () & kind);
2506 if (GetParentKind () & kind)
2507 return true;
2509 ParsingError (new ErrorEventArgs (MediaError, 3008, "ASX parse error"));
2511 return false;
2514 void
2515 PlaylistParser::ParsingError (ErrorEventArgs *args)
2517 LOG_PLAYLIST ("PlaylistParser::ParsingError (%s)\n", args->error_message);
2519 XML_StopParser (internal->parser, false);
2520 if (error_args) {
2521 if (args)
2522 args->unref ();
2523 return; // don't overwrite any previous errors.
2525 error_args = args; // don't ref, this method is called like this: ParsingError (new ErrorEventArgs (...));, so the caller gives us the ref he has
2529 PlaylistKind PlaylistParser::playlist_kinds [] = {
2530 /* ASX3 */
2531 PlaylistKind ("ABSTRACT", PlaylistKind::Abstract),
2532 PlaylistKind ("ASX", PlaylistKind::Asx),
2533 PlaylistKind ("ROOT", PlaylistKind::Root),
2534 PlaylistKind ("AUTHOR", PlaylistKind::Author),
2535 PlaylistKind ("BANNER", PlaylistKind::Banner),
2536 PlaylistKind ("BASE", PlaylistKind::Base),
2537 PlaylistKind ("COPYRIGHT", PlaylistKind::Copyright),
2538 PlaylistKind ("DURATION", PlaylistKind::Duration),
2539 PlaylistKind ("ENTRY", PlaylistKind::Entry),
2540 PlaylistKind ("ENTRYREF", PlaylistKind::EntryRef),
2541 PlaylistKind ("LOGURL", PlaylistKind::LogUrl),
2542 PlaylistKind ("MOREINFO", PlaylistKind::MoreInfo),
2543 PlaylistKind ("REF", PlaylistKind::Ref),
2544 PlaylistKind ("STARTTIME", PlaylistKind::StartTime),
2545 PlaylistKind ("TITLE", PlaylistKind::Title),
2546 PlaylistKind ("STARTMARKER", PlaylistKind::StartMarker),
2547 PlaylistKind ("REPEAT", PlaylistKind::Repeat),
2548 PlaylistKind ("ENDMARKER", PlaylistKind::EndMarker),
2549 PlaylistKind ("PARAM", PlaylistKind::Param),
2550 PlaylistKind ("EVENT", PlaylistKind::Event),
2552 PlaylistKind (NULL, PlaylistKind::Unknown)
2555 PlaylistKind::Kind
2556 PlaylistParser::StringToKind (const char *str)
2558 PlaylistKind::Kind kind = PlaylistKind::Unknown;
2560 for (int i = 0; playlist_kinds [i].str != NULL; i++) {
2561 if (str_match (str, playlist_kinds [i].str)) {
2562 kind = playlist_kinds [i].kind;
2563 break;
2567 LOG_PLAYLIST ("PlaylistParser::StringToKind ('%s') = %d\n", str, kind);
2569 return kind;
2572 const char *
2573 PlaylistParser::KindToString (PlaylistKind::Kind kind)
2575 const char *result = NULL;
2577 for (int i = 0; playlist_kinds [i].str != NULL; i++) {
2578 if (playlist_kinds [i].kind == kind) {
2579 result = playlist_kinds [i].str;
2580 break;
2584 LOG_PLAYLIST ("PlaylistParser::KindToString (%d) = '%s'\n", kind, result);
2586 return result;