2009-10-09 Chris Toshok <toshok@ximian.com>
[moon.git] / src / playlist.cpp
blob8feb1bf1902df2ccf69264d8be4a57903235eb7f
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;
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->GetErrorMessage() : "?");
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 media->PlayAsync ();
867 mplayer->Play ();
869 root->Emit (PlaylistRoot::PlayEvent);
872 void
873 PlaylistEntry::Pause ()
875 MediaPlayer *mplayer = GetMediaPlayer ();
876 PlaylistRoot *root = GetRoot ();
878 LOG_PLAYLIST ("PlaylistEntry::Pause ()\n");
880 g_return_if_fail (media != NULL);
881 g_return_if_fail (mplayer != NULL);
882 g_return_if_fail (root != NULL);
884 play_when_available = false;
885 media->PauseAsync ();
886 mplayer->Pause ();
888 root->Emit (PlaylistRoot::PauseEvent);
891 void
892 PlaylistEntry::Stop ()
894 LOG_PLAYLIST ("PlaylistEntry::Stop ()\n");
896 play_when_available = false;
897 if (media != NULL)
898 media->StopAsync ();
901 Media *
902 PlaylistEntry::GetMedia ()
904 return media;
907 bool
908 PlaylistEntry::IsSingleFile ()
910 return parent ? parent->IsSingleFile () : false;
913 PlaylistRoot *
914 PlaylistEntry::GetRoot ()
916 Playlist *pl;
918 if (IsDisposed ())
919 return NULL;
921 if (parent == NULL) {
922 g_return_val_if_fail (GetObjectType () == Type::PLAYLISTROOT, NULL);
923 return (PlaylistRoot *) this;
926 pl = parent;
928 while (pl->parent != NULL)
929 pl = pl->parent;
931 g_return_val_if_fail (pl->GetObjectType () == Type::PLAYLISTROOT, NULL);
933 return (PlaylistRoot *) pl;
937 * Playlist
940 Playlist::Playlist (Playlist *parent, IMediaSource *source)
941 : PlaylistEntry (Type::PLAYLIST, parent)
943 is_single_file = false;
944 waiting = false;
945 opened = false;
946 Init ();
947 this->source = source;
948 this->source->ref ();
951 Playlist::Playlist (Type::Kind kind)
952 : PlaylistEntry (kind)
954 LOG_PLAYLIST ("Playlist::Playlist ()\n");
955 is_single_file = true;
956 Init ();
958 AddEntry (new PlaylistEntry (this));
961 void
962 Playlist::Init ()
964 LOG_PLAYLIST ("Playlist::Init ()\n");
966 entries = new List ();
967 current_node = NULL;
968 source = NULL;
971 void
972 Playlist::Dispose ()
974 PlaylistNode *node;
975 PlaylistEntry *entry;
977 LOG_PLAYLIST ("Playlist::Dispose () id: %i\n", GET_OBJ_ID (this));
979 current_node = NULL;
981 if (entries != NULL) {
982 node = (PlaylistNode *) entries->First ();
983 while (node != NULL) {
984 entry = node->GetEntry ();
985 if (entry != NULL)
986 entry->Dispose ();
987 node = (PlaylistNode *) node->next;
989 delete entries;
990 entries = NULL;
993 if (source) {
994 source->unref ();
995 source = NULL;
998 PlaylistEntry::Dispose ();
1002 bool
1003 Playlist::IsCurrentEntryLastEntry ()
1005 PlaylistEntry *entry;
1006 Playlist *pl;
1008 if (entries->Last () == NULL)
1009 return false;
1011 if (current_node != entries->Last ())
1012 return false;
1014 entry = GetCurrentEntry ();
1016 if (!entry->IsPlaylist ())
1017 return true;
1019 pl = (Playlist *) entry;
1021 return pl->IsCurrentEntryLastEntry ();
1024 void
1025 Playlist::Open ()
1027 PlaylistEntry *current_entry;
1029 LOG_PLAYLIST ("Playlist::Open ()\n");
1031 current_node = (PlaylistNode *) entries->First ();
1033 current_entry = GetCurrentEntry ();
1035 while (current_entry && current_entry->HasDuration () && current_entry->GetDuration ()->HasTimeSpan() &&
1036 current_entry->GetDuration ()->GetTimeSpan () == 0) {
1037 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry->GetSourceName ()->ToString ());
1038 current_node = (PlaylistNode *) current_node->next;
1039 current_entry = GetCurrentEntry ();
1042 if (current_entry)
1043 current_entry->Open ();
1045 opened = true;
1047 LOG_PLAYLIST ("Playlist::Open (): current node: %p, current entry: %p\n", current_entry, GetCurrentEntry ());
1050 bool
1051 Playlist::PlayNext ()
1053 PlaylistEntry *current_entry;
1054 MediaElement *element = GetElement ();
1055 PlaylistRoot *root = GetRoot ();
1057 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p\n", current_node);
1058 g_return_val_if_fail (root != NULL, false);
1060 if (!current_node)
1061 return false;
1063 SetWaiting (false);
1065 current_entry = GetCurrentEntry ();
1067 if (current_entry->HasDuration() && current_entry->GetDuration()->IsForever ()) {
1068 element->SetPlayRequested ();
1069 current_entry->Play ();
1070 return true;
1073 if (current_entry->IsPlaylist ()) {
1074 Playlist *current_playlist = (Playlist *) current_entry;
1075 if (current_playlist->PlayNext ())
1076 return true;
1079 if (current_node->next) {
1080 current_node = (PlaylistNode *) current_node->next;
1082 current_entry = GetCurrentEntry ();
1083 if (current_entry) {
1084 LOG_PLAYLIST ("Playlist::PlayNext () playing entry: %p %s\n", current_entry, current_entry->GetFullSourceName ());
1085 element->SetPlayRequested ();
1086 root->Emit (PlaylistRoot::EntryChangedEvent);
1087 current_entry->Open ();
1088 return true;
1092 LOG_PLAYLIST ("Playlist::PlayNext () current_node: %p, nothing to play (is root: %i)\n", current_node, GetObjectType () == Type::PLAYLISTROOT);
1094 if (GetObjectType () == Type::PLAYLISTROOT)
1095 root->Emit (PlaylistRoot::MediaEndedEvent);
1097 return false;
1100 void
1101 Playlist::OnEntryEnded ()
1103 LOG_PLAYLIST ("Playlist::OnEntryEnded ()\n");
1104 PlayNext ();
1107 void
1108 Playlist::OnEntryFailed (ErrorEventArgs *args)
1110 bool fatal = true;
1111 PlaylistRoot *root = GetRoot ();
1113 LOG_PLAYLIST ("Playlist::OnEntryFailed () extended_code: %i is_single_file: %i\n", args ? args->GetExtendedCode() : 0, is_single_file);
1115 g_return_if_fail (root != NULL);
1117 // media or playlist 404: fatal
1118 // invalid playlist (playlist parsing failed): fatal
1119 // invalid media (gif, swf): play next
1120 if (args == NULL) {
1121 fatal = true;
1122 } else {
1123 // check if we're in a playlist
1124 if (GetMedia () != NULL && GetMedia ()->GetDemuxer () != NULL && GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER) {
1125 // we're a playlist
1126 if (args->GetExtendedCode() == MEDIA_UNKNOWN_CODEC) {
1127 fatal = false;
1128 } else {
1129 fatal = true;
1131 } else {
1132 // we're not a playlist
1133 fatal = true;
1137 if (fatal) {
1138 if (args)
1139 args->ref ();
1140 root->Emit (PlaylistRoot::MediaErrorEvent, args);
1141 } else {
1142 root->PlayNext ();
1146 void
1147 Playlist::Seek (guint64 pts)
1149 PlaylistEntry *current_entry;
1151 LOG_PLAYLIST ("Playlist::Seek (%llu)\n", pts);
1153 current_entry = GetCurrentEntry ();
1155 g_return_if_fail (current_entry != NULL);
1157 current_entry->Seek (pts);
1160 void
1161 Playlist::Play ()
1163 PlaylistEntry *current_entry;
1165 LOG_PLAYLIST ("Playlist::Play ()\n");
1167 current_entry = GetCurrentEntry ();
1169 g_return_if_fail (current_entry != NULL);
1171 if (current_entry && current_entry->HasDuration () && current_entry->GetDuration () == 0) {
1172 LOG_PLAYLIST ("Playlist::Open (), current entry (%s) has zero duration, skipping it.\n", current_entry->GetSourceName ()->ToString ());
1173 OnEntryEnded ();
1174 } else {
1175 if (current_entry)
1176 current_entry->Play ();
1180 void
1181 Playlist::Pause ()
1183 PlaylistEntry *current_entry;
1185 LOG_PLAYLIST ("Playlist::Pause ()\n");
1187 current_entry = GetCurrentEntry ();
1189 g_return_if_fail (current_entry != NULL);
1191 current_entry->Pause ();
1194 void
1195 Playlist::Stop ()
1197 PlaylistNode *node;
1199 LOG_PLAYLIST ("Playlist::Stop ()\n");
1201 node = (PlaylistNode *) entries->First ();
1202 current_node = node; // reset to first node
1203 while (node != NULL) {
1204 node->GetEntry ()->Stop ();
1205 node = (PlaylistNode *) node->next;
1209 void
1210 Playlist::PopulateMediaAttributes ()
1212 PlaylistEntry *current_entry = GetCurrentEntry ();
1214 LOG_PLAYLIST ("Playlist::PopulateMediaAttributes ()\n");
1216 if (!current_entry)
1217 return;
1219 current_entry->PopulateMediaAttributes ();
1222 void
1223 Playlist::AddEntry (PlaylistEntry *entry)
1225 PlaylistNode *node;
1227 LOG_PLAYLIST ("Playlist::AddEntry (%p) Count: %i\n", entry, entries->Length ());
1229 node = new PlaylistNode (entry);
1230 entries->Append (node);
1231 entry->unref ();
1233 if (entries->Length () == 1) {
1234 g_return_if_fail (current_node == NULL);
1235 current_node = node;
1239 bool
1240 Playlist::ReplaceCurrentEntry (Playlist *pl)
1242 bool result;
1244 PlaylistEntry *current_entry = GetCurrentEntry ();
1246 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntry (%p)\n", pl);
1248 // check for too nested playlist
1249 int counter = 0;
1250 PlaylistEntry *e = this;
1251 while (e != NULL && e->IsPlaylist ()) {
1252 if (e->GetObjectType () != Type::PLAYLISTROOT && e->GetMedia () != NULL && e->GetMedia ()->GetDemuxer () != NULL && e->GetMedia ()->GetDemuxer ()->GetObjectType () == Type::ASXDEMUXER)
1253 counter++;
1254 e = e->GetParent ();
1256 if (counter > 5) {
1257 ErrorEventArgs *args = new ErrorEventArgs (MediaError,
1258 MoonError (MoonError::EXCEPTION, 4001, "AG_E_NETWORK_ERROR"));
1259 OnEntryFailed (args);
1260 args->unref ();
1261 return false;
1265 if (current_entry->IsPlaylist ()) {
1266 result = ((Playlist *) current_entry)->ReplaceCurrentEntry (pl);
1267 } else {
1268 PlaylistNode *pln = new PlaylistNode (pl);
1269 pl->MergeWith (current_entry);
1270 entries->InsertBefore (pln, current_node);
1271 entries->Remove (current_node);
1272 pl->SetParent (this);
1273 current_node = pln;
1274 result = true;
1277 LOG_PLAYLIST ("Playlist::ReplaceCurrentEntrY (%p) [DONE]\n", pl);
1279 return result;
1282 void
1283 Playlist::MergeWith (PlaylistEntry *entry)
1285 LOG_PLAYLIST ("Playlist::MergeWith (%p)\n", entry);
1287 SetBase (entry->GetBase () ? new Uri (*entry->GetBase ()) : NULL);
1288 SetTitle (entry->GetTitle ());
1289 SetAuthor (entry->GetAuthor ());
1290 SetAbstract (entry->GetAbstract ());
1291 SetCopyright (entry->GetCopyright ());
1293 SetSourceName (entry->GetSourceName () ? new Uri (*entry->GetSourceName ()) : NULL);
1294 if (entry->HasDuration ())
1295 SetDuration (entry->GetDuration ());
1296 Initialize (entry->GetMedia ());
1297 entry->ClearMedia ();
1300 PlaylistEntry *
1301 Playlist::GetCurrentPlaylistEntry ()
1303 PlaylistEntry *result = NULL;
1305 if (current_node)
1306 result = current_node->GetEntry () ->GetCurrentPlaylistEntry ();
1308 return result;
1311 * PlaylistRoot
1314 PlaylistRoot::PlaylistRoot (MediaElement *element)
1315 : Playlist (Type::PLAYLISTROOT)
1317 this->element = element;
1319 seek_pts = 0;
1320 mplayer = element->GetMediaPlayer ();
1321 mplayer->AddHandler (MediaPlayer::MediaEndedEvent, MediaEndedCallback, this);
1322 mplayer->AddHandler (MediaPlayer::BufferUnderflowEvent, BufferUnderflowCallback, this);
1323 mplayer->ref ();
1326 void
1327 PlaylistRoot::Dispose ()
1329 if (mplayer != NULL) {
1330 mplayer->RemoveAllHandlers (this);
1331 mplayer->unref ();
1332 mplayer = NULL;
1335 Playlist::Dispose ();
1338 bool
1339 PlaylistRoot::IsSingleFile ()
1341 PlaylistEntry *entry;
1343 if (GetCount () != 1)
1344 return false;
1346 entry = GetCurrentEntry ();
1347 if (entry == NULL)
1348 return false;
1350 if (entry->GetObjectType () == Type::PLAYLISTENTRY)
1351 return true;
1353 return entry->IsSingleFile ();
1356 #if DEBUG
1357 void
1358 PlaylistEntry::DumpInternal (int tabs)
1360 printf ("%*s%s %i\n", tabs, "", GetTypeName (), GET_OBJ_ID (this));
1361 tabs++;
1362 printf ("%*sParent: %p %s\n", tabs, "", parent, parent ? parent->GetTypeName () : NULL);
1363 printf ("%*sFullSourceName: %s\n", tabs, "", GetFullSourceName ());
1364 printf ("%*sDuration: %s %.2f seconds\n", tabs, "", HasDuration () ? "yes" : "no", HasDuration () ? GetDuration ()->ToSecondsFloat () : 0.0);
1365 printf ("%*sMedia: %i %s\n", tabs, "", GET_OBJ_ID (media), media ? "" : "(null)");
1366 if (media) {
1367 printf ("%*sUri: %s\n", tabs, "", media->GetUri ());
1368 printf ("%*sDemuxer: %i %s\n", tabs, "", GET_OBJ_ID (media->GetDemuxer ()), media->GetDemuxer () ? media->GetDemuxer ()->GetTypeName () : "N/A");
1369 printf ("%*sSource: %i %s\n", tabs, "", GET_OBJ_ID (media->GetSource ()), media->GetSource () ? media->GetSource ()->GetTypeName () : "N/A");
1374 void
1375 Playlist::DumpInternal (int tabs)
1377 PlaylistNode *node;
1379 PlaylistEntry::DumpInternal (tabs);
1380 printf ("%*s %i entries:\n", tabs, "", entries->Length ());
1381 node = (PlaylistNode *) entries->First ();
1382 while (node != NULL) {
1383 if (node == current_node)
1384 printf ("*%*s * CURRENT NODE *\n", tabs, "");
1385 node->GetEntry ()->DumpInternal (tabs + 2);
1386 node = (PlaylistNode *) node->next;
1389 void
1390 PlaylistRoot::Dump ()
1392 printf ("\n\nDUMP OF PLAYLIST\n\n");
1393 DumpInternal (0);
1394 printf ("\n\nDUMP OF PLAYLIST DONE\n\n");
1396 #endif
1398 void
1399 PlaylistRoot::SeekCallback (EventObject *obj)
1401 PlaylistRoot *playlist = (PlaylistRoot *) obj;
1403 LOG_PLAYLIST ("Playlist::SeekCallback () pts: %" G_GUINT64_FORMAT "\n", playlist->seek_pts);
1405 if (playlist->IsDisposed ())
1406 return;
1408 if (playlist->seek_pts != G_MAXUINT64) {
1409 guint64 pts = playlist->seek_pts;
1410 playlist->seek_pts = G_MAXUINT64;
1411 playlist->Seek (pts);
1415 void
1416 PlaylistRoot::SeekAsync (guint64 pts)
1418 LOG_PLAYLIST ("Playlist::SeekAsync (%" G_GUINT64_FORMAT ")\n", pts);
1419 seek_pts = pts;
1420 AddTickCall (SeekCallback);
1423 void
1424 PlaylistRoot::PlayCallback (EventObject *obj)
1426 LOG_PLAYLIST ("Playlist::PlayCallback ()\n");
1428 PlaylistRoot *root = (PlaylistRoot *) obj;
1429 if (root->IsDisposed ())
1430 return;
1431 root->Play ();
1434 void
1435 PlaylistRoot::PlayAsync ()
1437 LOG_PLAYLIST ("Playlist::PlayAsync ()\n");
1438 AddTickCall (PlayCallback);
1441 void
1442 PlaylistRoot::PauseCallback (EventObject *obj)
1444 LOG_PLAYLIST ("Playlist::PauseCallback ()\n");
1446 PlaylistRoot *root = (PlaylistRoot *) obj;
1447 if (root->IsDisposed ())
1448 return;
1449 root->Pause ();
1452 void
1453 PlaylistRoot::PauseAsync ()
1455 LOG_PLAYLIST ("Playlist::PauseAsync ()\n");
1456 AddTickCall (PauseCallback);
1459 void
1460 PlaylistRoot::OpenCallback (EventObject *obj)
1462 LOG_PLAYLIST ("Playlist::OpenCallback ()\n");
1464 PlaylistRoot *root = (PlaylistRoot *) obj;
1465 if (root->IsDisposed ())
1466 return;
1467 root->Open ();
1470 void
1471 PlaylistRoot::OpenAsync ()
1473 LOG_PLAYLIST ("Playlist::OpenAsync ()\n");
1474 AddTickCall (OpenCallback);
1477 void
1478 PlaylistRoot::StopCallback (EventObject *obj)
1480 LOG_PLAYLIST ("Playlist::StopCallback ()\n");
1482 PlaylistRoot *root = (PlaylistRoot *) obj;
1483 if (root->IsDisposed ())
1484 return;
1485 root->Stop ();
1488 void
1489 PlaylistRoot::StopAsync ()
1491 LOG_PLAYLIST ("Playlist::StopAsync ()\n");
1492 AddTickCall (StopCallback);
1495 void
1496 PlaylistRoot::Stop ()
1498 MediaPlayer *mplayer;
1500 LOG_PLAYLIST ("PlaylistRoot::Stop ()\n");
1502 mplayer = GetMediaPlayer ();
1504 Playlist::Stop ();
1505 if (mplayer != NULL)
1506 mplayer->Stop ();
1507 // Stop is called async, and if we now emit Open async, we'd possibly not get events in the right order
1508 // example with user code:
1509 // Stop ();
1510 // Play ();
1511 // would end up like:
1512 // StopAsync (); -> enqueue Stop
1513 // PlayAsync (); -> enqueue Play
1514 // Stop is called, enqueue Open
1515 Open ();
1516 Emit (StopEvent); // we emit the event after enqueuing the Open request, do avoid funky side-effects of event emission.
1519 void
1520 PlaylistRoot::EmitBufferUnderflowEvent (EventObject *obj)
1522 PlaylistRoot *root = (PlaylistRoot *) obj;
1523 root->Emit (BufferUnderflowEvent);
1526 MediaPlayer *
1527 PlaylistRoot::GetMediaPlayer ()
1529 return mplayer;
1532 Media *
1533 PlaylistRoot::GetCurrentMedia ()
1535 PlaylistEntry *entry = GetCurrentEntry ();
1537 if (entry == NULL)
1538 return NULL;
1540 return entry->GetMedia ();
1543 MediaElement *
1544 PlaylistRoot::GetElement ()
1546 return element;
1549 void
1550 PlaylistRoot::MediaEndedHandler (MediaPlayer *mplayer, EventArgs *args)
1552 LOG_PLAYLIST ("PlaylistRoot::MediaEndedHandler (%p, %p)\n", mplayer, args);
1554 OnEntryEnded ();
1556 // Emit (MediaEndedEvent, args);
1559 void
1560 PlaylistRoot::BufferUnderflowHandler (MediaPlayer *mplayer, EventArgs *args)
1562 LOG_PLAYLIST ("PlaylistRoot::BufferUnderflowHandler (%p, %p)\n", mplayer, args);
1564 if (Surface::InMainThread ()) {
1565 EmitBufferUnderflowEvent (this);
1566 } else {
1567 AddTickCall (EmitBufferUnderflowEvent);
1572 * PlaylistParser
1575 PlaylistParser::PlaylistParser (PlaylistRoot *root, IMediaSource *source)
1577 this->root = root;
1578 this->source = source;
1579 this->internal = NULL;
1580 this->kind_stack = NULL;
1581 this->playlist = NULL;
1582 this->current_entry = NULL;
1583 this->current_text = NULL;
1584 this->error_args = NULL;
1587 void
1588 PlaylistParser::SetSource (IMediaSource *new_source)
1590 if (source)
1591 source->unref ();
1592 source = new_source;
1593 if (source)
1594 source->ref ();
1597 void
1598 PlaylistParser::Setup (XmlType type)
1600 playlist = NULL;
1601 current_entry = NULL;
1602 current_text = NULL;
1604 was_playlist = false;
1606 internal = new PlaylistParserInternal ();
1607 kind_stack = new List ();
1608 PushCurrentKind (PlaylistKind::Root);
1610 if (type == XML_TYPE_ASX3) {
1611 XML_SetUserData (internal->parser, this);
1612 XML_SetElementHandler (internal->parser, on_asx_start_element, on_asx_end_element);
1613 XML_SetCharacterDataHandler (internal->parser, on_asx_text);
1618 void
1619 PlaylistParser::Cleanup ()
1621 if (kind_stack) {
1622 kind_stack->Clear (true);
1623 delete kind_stack;
1624 kind_stack = NULL;
1626 delete internal;
1627 internal = NULL;
1628 if (playlist) {
1629 playlist->unref ();
1630 playlist = NULL;
1632 if (error_args) {
1633 error_args->unref ();
1634 error_args = NULL;
1638 PlaylistParser::~PlaylistParser ()
1640 Cleanup ();
1643 static bool
1644 str_match (const char *candidate, const char *tag)
1646 return g_ascii_strcasecmp (candidate, tag) == 0;
1649 void
1650 PlaylistParser::on_asx_start_element (gpointer user_data, const char *name, const char **attrs)
1652 ((PlaylistParser *) user_data)->OnASXStartElement (name, attrs);
1655 void
1656 PlaylistParser::on_asx_end_element (gpointer user_data, const char *name)
1658 ((PlaylistParser *) user_data)->OnASXEndElement (name);
1661 void
1662 PlaylistParser::on_asx_text (gpointer user_data, const char *data, int len)
1664 ((PlaylistParser *) user_data)->OnASXText (data, len);
1667 static bool
1668 is_all_whitespace (const char *str)
1670 if (str == NULL)
1671 return true;
1673 for (int i = 0; str [i] != 0; i++) {
1674 switch (str [i]) {
1675 case 10:
1676 case 13:
1677 case ' ':
1678 case '\t':
1679 break;
1680 default:
1681 return false;
1684 return true;
1688 // To make matters more interesting, the format of the VALUE attribute in the STARTTIME tag isn't
1689 // exactly the same as xaml's or javascript's TimeSpan format.
1691 // The time index, in hours, minutes, seconds, and hundredths of seconds.
1692 // [[hh]:mm]:ss.fract
1694 // The parser seems to stop if it finds a second dot, returnning whatever it had parsed
1695 // up till then
1697 // At most 4 digits of fract is read, the rest is ignored (even if it's not numbers).
1699 static bool
1700 parse_int (const char **pp, const char *end, int *result)
1702 const char *p = *pp;
1703 int res = 0;
1704 bool success = false;
1706 while (p <= end && g_ascii_isdigit (*p)) {
1707 res = res * 10 + *p - '0';
1708 p++;
1711 success = *pp != p;
1713 *pp = p;
1714 *result = res;
1716 return success;
1719 static bool
1720 duration_from_asx_str (PlaylistParser *parser, const char *str, Duration **res)
1722 const char *end = str + strlen (str);
1723 const char *p;
1725 int values [] = {0, 0, 0};
1726 int counter = 0;
1727 int hh = 0, mm = 0, ss = 0;
1728 int milliseconds = 0;
1729 int digits = 2;
1731 p = str;
1733 if (!g_ascii_isdigit (*p)) {
1734 parser->ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 2210, "AG_E_INVALID_ARGUMENT")));
1735 return false;
1738 for (int i = 0; i < 3; i++) {
1739 if (!parse_int (&p, end, &values [i])) {
1740 parser->ParsingError (new ErrorEventArgs (MediaError,
1741 MoonError (MoonError::EXCEPTION, 2210, "AG_E_INVALID_ARGUMENT")));
1742 return false;
1744 counter++;
1745 if (*p != ':')
1746 break;
1747 p++;
1750 if (*p == '.') {
1751 p++;
1752 while (digits >= 0 && g_ascii_isdigit (*p)) {
1753 milliseconds += pow (10.0f, digits) * (*p - '0');
1754 p++;
1755 digits--;
1757 if (counter == 3 && *p != 0 && !g_ascii_isdigit (*p)) {
1758 parser->ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 2210, "AG_E_INVALID_ARGUMENT")));
1759 return false;
1763 switch (counter) {
1764 case 1:
1765 ss = values [0];
1766 break;
1767 case 2:
1768 ss = values [1];
1769 mm = values [0];
1770 break;
1771 case 3:
1772 ss = values [2];
1773 mm = values [1];
1774 hh = values [0];
1775 break;
1776 default:
1777 parser->ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 2210, "AG_E_INVALID_ARGUMENT")));
1778 return false;
1781 gint64 ms = ((hh * 3600) + (mm * 60) + ss) * 1000 + milliseconds;
1782 TimeSpan result = TimeSpan_FromPts (MilliSeconds_ToPts (ms));
1783 Duration *duration = new Duration (result);
1785 *res = duration;
1787 return true;
1790 void
1791 PlaylistParser::OnASXStartElement (const char *name, const char **attrs)
1793 PlaylistKind::Kind kind = StringToKind (name);
1794 Uri *uri = NULL;
1795 bool failed;
1797 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), kind = %d\n", name, attrs, kind);
1799 g_free (current_text);
1800 current_text = NULL;
1802 PushCurrentKind (kind);
1804 switch (kind) {
1805 case PlaylistKind::Abstract:
1806 if (attrs != NULL && attrs [0] != NULL)
1807 ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1808 break;
1809 case PlaylistKind::Asx:
1810 // Here the kind stack should be: Root+Asx
1811 if (kind_stack->Length () != 2 || !AssertParentKind (PlaylistKind::Root)) {
1812 ParsingError (new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
1813 return;
1816 playlist = new Playlist (root, source);
1818 for (int i = 0; attrs [i] != NULL; i += 2) {
1819 if (str_match (attrs [i], "VERSION")) {
1820 if (str_match (attrs [i+1], "3")) {
1821 playlist_version = 3;
1822 } else if (str_match (attrs [i+1], "3.0")) {
1823 playlist_version = 3;
1824 } else {
1825 ParsingError (new ErrorEventArgs (MediaError,
1826 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
1828 } else if (str_match (attrs [i], "BANNERBAR")) {
1829 ParsingError (new ErrorEventArgs (MediaError,
1830 MoonError (MoonError::EXCEPTION, 3007, "Unsupported ASX attribute")));
1831 } else if (str_match (attrs [i], "PREVIEWMODE")) {
1832 ParsingError (new ErrorEventArgs (MediaError,
1833 MoonError (MoonError::EXCEPTION, 3007, "Unsupported ASX attribute")));
1834 } else {
1835 ParsingError (new ErrorEventArgs (MediaError,
1836 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1839 break;
1840 case PlaylistKind::Author:
1841 if (attrs != NULL && attrs [0] != NULL)
1842 ParsingError (new ErrorEventArgs (MediaError,
1843 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1844 break;
1845 case PlaylistKind::Banner:
1846 if (attrs != NULL && attrs [0] != NULL)
1847 ParsingError (new ErrorEventArgs (MediaError,
1848 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1849 break;
1850 case PlaylistKind::Base:
1851 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
1852 break;
1853 for (int i = 0; attrs [i] != NULL; i += 2) {
1854 if (str_match (attrs [i], "HREF")) {
1855 // TODO: What do we do with this value?
1856 if (GetCurrentContent () != NULL) {
1857 failed = false;
1858 uri = new Uri ();
1859 if (!uri->Parse (attrs [i+1], true)) {
1860 failed = true;
1861 } else if (uri->GetScheme() == NULL) {
1862 failed = true;
1863 } else if (uri->IsScheme ("http") &&
1864 uri->IsScheme ("https") &&
1865 uri->IsScheme ("mms") &&
1866 uri->IsScheme ("rtsp") &&
1867 uri->IsScheme ("rstpt")) {
1868 failed = true;
1871 if (!failed) {
1872 GetCurrentContent ()->SetBase (uri);
1873 } else {
1874 delete uri;
1875 ParsingError (new ErrorEventArgs (MediaError,
1876 MoonError (MoonError::EXCEPTION, 4001, "AG_E_NETWORK_ERROR")));
1878 uri = NULL;
1880 } else {
1881 ParsingError (new ErrorEventArgs (MediaError,
1882 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1883 break;
1886 break;
1887 case PlaylistKind::Copyright:
1888 if (attrs != NULL && attrs [0] != NULL)
1889 ParsingError (new ErrorEventArgs (MediaError,
1890 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1891 break;
1892 case PlaylistKind::Duration: {
1893 Duration *dur;
1894 for (int i = 0; attrs [i] != NULL; i += 2) {
1895 if (str_match (attrs [i], "VALUE")) {
1896 if (duration_from_asx_str (this, attrs [i+1], &dur)) {
1897 if (GetCurrentEntry () != NULL && GetParentKind () != PlaylistKind::Ref) {
1898 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name, attrs, TimeSpan_ToSecondsFloat (dur->GetTimeSpan()));
1899 GetCurrentEntry ()->SetDuration (dur);
1902 } else {
1903 ParsingError (new ErrorEventArgs (MediaError,
1904 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1905 break;
1908 break;
1910 case PlaylistKind::Entry: {
1911 bool client_skip = true;
1912 for (int i = 0; attrs [i] != NULL; i += 2) {
1913 if (str_match (attrs [i], "CLIENTSKIP")) {
1914 // TODO: What do we do with this value?
1915 if (str_match (attrs [i+1], "YES")) {
1916 client_skip = true;
1917 } else if (str_match (attrs [i+1], "NO")) {
1918 client_skip = false;
1919 } else {
1920 ParsingError (new ErrorEventArgs (MediaError,
1921 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
1922 break;
1924 } else if (str_match (attrs [i], "SKIPIFREF")) {
1925 ParsingError (new ErrorEventArgs (MediaError,
1926 MoonError (MoonError::EXCEPTION, 3007, "Unsupported ASX attribute")));
1927 break;
1928 } else {
1929 ParsingError (new ErrorEventArgs (MediaError,
1930 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1931 break;
1934 PlaylistEntry *entry = new PlaylistEntry (playlist);
1935 entry->SetClientSkip (client_skip);
1936 playlist->AddEntry (entry);
1937 current_entry = entry;
1938 break;
1940 case PlaylistKind::EntryRef: {
1941 char *href = NULL;
1942 for (int i = 0; attrs [i] != NULL; i += 2) {
1943 if (str_match (attrs [i], "HREF")) {
1944 if (href == NULL)
1945 href = g_strdup (attrs [i+1]);
1946 // Docs says this attribute isn't unsupported, but an error is emitted.
1947 //} else if (str_match (attrs [i], "CLIENTBIND")) {
1948 // // TODO: What do we do with this value?
1949 } else {
1950 ParsingError (new ErrorEventArgs (MediaError,
1951 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1952 break;
1956 if (href) {
1957 uri = new Uri ();
1958 if (!uri->Parse (href)) {
1959 delete uri;
1960 uri = NULL;
1961 ParsingError (new ErrorEventArgs (MediaError,
1962 MoonError (MoonError::EXCEPTION, 1001, "AG_E_UNKNOWN_ERROR")));
1966 PlaylistEntry *entry = new PlaylistEntry (playlist);
1967 if (uri)
1968 entry->SetSourceName (uri);
1969 uri = NULL;
1970 playlist->AddEntry (entry);
1971 current_entry = entry;
1972 break;
1974 case PlaylistKind::LogUrl:
1975 if (attrs != NULL && attrs [0] != NULL)
1976 ParsingError (new ErrorEventArgs (MediaError,
1977 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1978 break;
1979 case PlaylistKind::MoreInfo:
1980 for (int i = 0; attrs [i] != NULL; i += 2) {
1981 if (str_match (attrs [i], "HREF")) {
1982 if (GetCurrentEntry () != NULL)
1983 GetCurrentEntry ()->SetInfoURL (attrs [i+1]);
1984 } else if (str_match (attrs [i], "TARGET")) {
1985 if (GetCurrentEntry () != NULL)
1986 GetCurrentEntry ()->SetInfoTarget (attrs [i+1]);
1987 } else {
1988 ParsingError (new ErrorEventArgs (MediaError,
1989 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
1990 break;
1993 break;
1994 case PlaylistKind::StartTime: {
1995 Duration *dur;
1996 for (int i = 0; attrs [i] != NULL; i += 2) {
1997 if (str_match (attrs [i], "VALUE")) {
1998 if (duration_from_asx_str (this, attrs [i+1], &dur) && dur->HasTimeSpan ()) {
1999 if (GetCurrentEntry () != NULL && GetParentKind () != PlaylistKind::Ref) {
2000 LOG_PLAYLIST ("PlaylistParser::OnStartElement (%s, %p), found VALUE/timespan = %f s\n", name, attrs, TimeSpan_ToSecondsFloat (dur->GetTimeSpan()));
2001 GetCurrentEntry ()->SetStartTime (dur->GetTimeSpan ());
2004 } else {
2005 ParsingError (new ErrorEventArgs (MediaError,
2006 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2007 break;
2011 break;
2013 case PlaylistKind::Ref: {
2014 for (int i = 0; attrs [i] != NULL; i += 2) {
2015 if (str_match (attrs [i], "HREF")) {
2016 if (GetCurrentEntry () != NULL && GetCurrentEntry ()->GetSourceName () == NULL) {
2017 uri = new Uri ();
2018 if (uri->Parse (attrs [i+1])) {
2019 GetCurrentEntry ()->SetSourceName (uri);
2020 } else {
2021 delete uri;
2022 ParsingError (new ErrorEventArgs (MediaError,
2023 MoonError (MoonError::EXCEPTION, 1001, "AG_E_UNKNOWN_ERROR")));
2025 uri = NULL;
2027 } else {
2028 ParsingError (new ErrorEventArgs (MediaError,
2029 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2030 break;
2033 break;
2035 case PlaylistKind::Param: {
2036 const char *name = NULL;
2037 const char *value = NULL;
2039 for (int i = 0; attrs [i] != NULL; i+= 2) {
2040 if (str_match (attrs [i], "name")) {
2041 name = attrs [i + 1];
2042 } else if (str_match (attrs [i], "value")) {
2043 value = attrs [i + 1];
2044 } else {
2045 ParsingError (new ErrorEventArgs (MediaError,
2046 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2047 break;
2050 if (value != NULL && value [0] != 0 && name != NULL && name [0] != 0) {
2051 PlaylistEntry *entry = GetCurrentEntry ();
2052 if (entry == NULL)
2053 entry = playlist;
2054 if (entry == NULL)
2055 entry = root;
2056 entry->AddParams (name, value);
2057 } else {
2058 // TODO: add test
2060 break;
2062 case PlaylistKind::Title:
2063 if (attrs != NULL && attrs [0] != NULL)
2064 ParsingError (new ErrorEventArgs (MediaError,
2065 MoonError (MoonError::EXCEPTION, 3005, "Invalid ASX attribute")));
2066 break;
2067 case PlaylistKind::StartMarker:
2068 case PlaylistKind::EndMarker:
2069 case PlaylistKind::Repeat:
2070 case PlaylistKind::Event:
2071 ParsingError (new ErrorEventArgs (MediaError,
2072 MoonError (MoonError::EXCEPTION, 3006, "Unsupported ASX element")));
2073 break;
2074 case PlaylistKind::Root:
2075 case PlaylistKind::Unknown:
2076 default:
2077 LOG_PLAYLIST ("PlaylistParser::OnStartElement ('%s', %p): Unknown kind: %d\n", name, attrs, kind);
2078 ParsingError (new ErrorEventArgs (MediaError,
2079 MoonError (MoonError::EXCEPTION, 3004, "Invalid ASX element")));
2080 break;
2084 void
2085 PlaylistParser::OnASXEndElement (const char *name)
2087 PlaylistKind::Kind kind = GetCurrentKind ();
2088 Duration *dur;
2090 LOG_PLAYLIST ("PlaylistParser::OnEndElement (%s), GetCurrentKind (): %d, GetCurrentKind () to string: %s\n", name, kind, KindToString (kind));
2092 switch (kind) {
2093 case PlaylistKind::Abstract:
2094 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2095 break;
2096 if (GetCurrentContent () != NULL)
2097 GetCurrentContent ()->SetAbstract (current_text);
2098 break;
2099 case PlaylistKind::Author:
2100 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2101 break;
2102 if (GetCurrentContent () != NULL)
2103 GetCurrentContent ()->SetAuthor (current_text);
2104 break;
2105 case PlaylistKind::Base:
2106 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2107 break;
2108 break;
2109 case PlaylistKind::Copyright:
2110 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2111 break;
2112 if (GetCurrentContent () != NULL)
2113 GetCurrentContent ()->SetCopyright (current_text);
2114 break;
2115 case PlaylistKind::Duration:
2116 if (!AssertParentKind (PlaylistKind::Entry | PlaylistKind::Ref))
2117 break;
2118 if (current_text == NULL)
2119 break;
2120 duration_from_asx_str (this, current_text, &dur);
2121 if (GetCurrentEntry () != NULL)
2122 GetCurrentEntry ()->SetDuration (dur);
2123 break;
2124 case PlaylistKind::Entry:
2125 if (!AssertParentKind (PlaylistKind::Asx))
2126 break;
2127 if (!is_all_whitespace (current_text)) {
2128 ParsingError (new ErrorEventArgs (MediaError,
2129 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2131 break;
2132 case PlaylistKind::EntryRef:
2133 if (!AssertParentKind (PlaylistKind::Asx))
2134 break;
2135 break;
2136 case PlaylistKind::StartTime:
2137 if (!AssertParentKind (PlaylistKind::Entry | PlaylistKind::Ref))
2138 break;
2139 if (!is_all_whitespace (current_text)) {
2140 ParsingError (new ErrorEventArgs (MediaError,
2141 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2143 break;
2144 case PlaylistKind::Title:
2145 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2146 break;
2147 if (GetCurrentContent () != NULL)
2148 GetCurrentContent ()->SetTitle (current_text);
2149 break;
2150 case PlaylistKind::Asx:
2151 if (playlist_version == 3)
2152 was_playlist = true;
2153 if (!AssertParentKind (PlaylistKind::Root))
2154 break;
2155 break;
2156 case PlaylistKind::Ref:
2157 if (!AssertParentKind (PlaylistKind::Entry))
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::MoreInfo:
2165 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2166 break;
2167 if (!is_all_whitespace (current_text)) {
2168 ParsingError (new ErrorEventArgs (MediaError,
2169 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2171 break;
2172 case PlaylistKind::Param:
2173 if (!AssertParentKind (PlaylistKind::Asx | PlaylistKind::Entry))
2174 break;
2175 if (!is_all_whitespace (current_text)) {
2176 ParsingError (new ErrorEventArgs (MediaError,
2177 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2179 break;
2180 default:
2181 LOG_PLAYLIST ("PlaylistParser::OnEndElement ('%s'): Unknown kind %d.\n", name, kind);
2182 ParsingError (new ErrorEventArgs (MediaError,
2183 MoonError (MoonError::EXCEPTION, 3004, "Invalid ASX element")));
2184 break;
2187 if (current_text != NULL) {
2188 g_free (current_text);
2189 current_text = NULL;
2192 switch (GetCurrentKind ()) {
2193 case PlaylistKind::Entry:
2194 EndEntry ();
2195 break;
2196 default:
2197 break;
2199 PopCurrentKind ();
2202 void
2203 PlaylistParser::OnASXText (const char *text, int len)
2205 char *a = g_strndup (text, len);
2207 #if DEBUG
2208 char *p = g_strndup (text, len);
2209 for (int i = 0; p [i] != 0; i++)
2210 if (p [i] == 10 || p [i] == 13)
2211 p [i] = ' ';
2213 LOG_PLAYLIST ("PlaylistParser::OnText (%s, %d)\n", p, len);
2214 g_free (p);
2215 #endif
2217 if (current_text == NULL) {
2218 current_text = a;
2219 } else {
2220 char *b = g_strconcat (current_text, a, NULL);
2221 g_free (current_text);
2222 current_text = b;
2226 bool
2227 PlaylistParser::Is (IMediaSource *source, const char *asx_header)
2229 bool result = false;
2230 int asx_header_length = strlen (asx_header);
2231 unsigned char buffer [20];
2233 do {
2234 result = source->Peek ((guint8 *) buffer, asx_header_length);
2235 if (!result)
2236 goto cleanup;
2238 // skip any whitespace
2239 unsigned char c = buffer [0];
2240 switch (c) {
2241 case ' ':
2242 case '\t':
2243 case 10:
2244 case 13: {
2245 result = source->ReadAll ((guint8 *) buffer, 1);
2246 if (!result)
2247 goto cleanup;
2248 continue;
2250 case 0xef: {
2251 if (buffer [1] == 0xbb && buffer [2] == 0xbf) { // UTF-8 BOM: EF BB BF
2252 result = source->ReadAll ((guint8 *) buffer, 3);
2253 if (!result)
2254 goto cleanup;
2255 continue;
2257 // TODO: there might be other BOMs we should handle too
2258 // fall through
2260 default:
2261 result = !g_ascii_strncasecmp ((const char *) buffer, asx_header, asx_header_length);
2262 goto cleanup;
2264 } while (true);
2266 cleanup:
2268 source->Seek (0, SEEK_SET);
2270 return result;
2273 bool
2274 PlaylistParser::IsASX3 (IMediaSource *source)
2276 return Is (source, "<ASX");
2279 bool
2280 PlaylistParser::IsASX2 (IMediaSource *source)
2282 return Is (source, "[Reference]");
2285 bool
2286 PlaylistParser::ParseASX2 ()
2288 const int BUFFER_SIZE = 1024;
2289 int bytes_read;
2290 char buffer[BUFFER_SIZE];
2291 char *ref;
2292 char *mms_uri;
2293 GKeyFile *key_file;
2294 Uri *uri;
2296 playlist_version = 2;
2298 bytes_read = source->ReadSome (buffer, BUFFER_SIZE);
2299 if (bytes_read < 0) {
2300 LOG_PLAYLIST_WARN ("Could not read asx document for parsing.\n");
2301 return false;
2304 key_file = g_key_file_new ();
2305 if (!g_key_file_load_from_data (key_file, buffer, bytes_read,
2306 G_KEY_FILE_NONE, NULL)) {
2307 LOG_PLAYLIST_WARN ("Invalid asx2 document.\n");
2308 g_key_file_free (key_file);
2309 return false;
2312 ref = g_key_file_get_value (key_file, "Reference", "Ref1", NULL);
2313 if (ref == NULL) {
2314 LOG_PLAYLIST_WARN ("Could not find Ref1 entry in asx2 document.\n");
2315 g_key_file_free (key_file);
2316 return false;
2319 if (!g_str_has_prefix (ref, "http://") || !g_str_has_suffix (ref, "MSWMExt=.asf")) {
2320 LOG_PLAYLIST_WARN ("Could not find a valid uri within Ref1 entry in asx2 document.\n");
2321 g_free (ref);
2322 g_key_file_free (key_file);
2323 return false;
2326 mms_uri = g_strdup_printf ("mms://%s", strstr (ref, "http://") + strlen ("http://"));
2327 g_free (ref);
2328 g_key_file_free (key_file);
2331 playlist = new Playlist (root, source);
2333 PlaylistEntry *entry = new PlaylistEntry (playlist);
2334 uri = new Uri ();
2335 if (uri->Parse (mms_uri)) {
2336 entry->SetSourceName (uri);
2337 } else {
2338 delete uri;
2340 playlist->AddEntry (entry);
2341 current_entry = entry;
2343 return true;
2346 bool
2347 PlaylistParser::TryFixError (gint8 *current_buffer, int bytes_read)
2349 Media *media;
2351 if (XML_GetErrorCode (internal->parser) != XML_ERROR_INVALID_TOKEN)
2352 return false;
2354 int index = XML_GetErrorByteIndex (internal->parser);
2356 if (index > bytes_read)
2357 return false;
2359 LOG_PLAYLIST ("Attempting to fix invalid token error index: %d\n", index);
2361 // OK, so we are going to guess that we are in an attribute here and walk back
2362 // until we hit a control char that should be escaped.
2363 char * escape = NULL;
2364 while (index >= 0) {
2365 switch (current_buffer [index]) {
2366 case '&':
2367 escape = g_strdup ("&amp;");
2368 break;
2369 case '<':
2370 escape = g_strdup ("&lt;");
2371 break;
2372 case '>':
2373 escape = g_strdup ("&gt;");
2374 break;
2375 case '\"':
2376 break;
2378 if (escape)
2379 break;
2380 index--;
2383 if (!escape) {
2384 LOG_PLAYLIST_WARN ("Unable to find an invalid escape character to fix in ASX: %s.\n", current_buffer);
2385 g_free (escape);
2386 return false;
2389 int escape_len = strlen (escape);
2390 int new_size = source->GetSize () + escape_len - 1;
2391 int patched_size = internal->bytes_read + bytes_read + escape_len - 1;
2392 gint8 * new_buffer = (gint8 *) g_malloc (new_size);
2394 source->Seek (0, SEEK_SET);
2395 source->ReadSome (new_buffer, internal->bytes_read);
2397 memcpy (new_buffer + internal->bytes_read, current_buffer, index);
2398 memcpy (new_buffer + internal->bytes_read + index, escape, escape_len);
2399 memcpy (new_buffer + internal->bytes_read + index + escape_len, current_buffer + index + 1, bytes_read - index - 1);
2401 source->Seek (internal->bytes_read + bytes_read, SEEK_SET);
2402 source->ReadSome (new_buffer + patched_size, new_size - patched_size);
2404 media = source->GetMediaReffed ();
2406 MemorySource *reparse_source = new MemorySource (media, new_buffer, new_size);
2407 SetSource (reparse_source);
2408 reparse_source->unref ();
2410 internal->reparse = true;
2412 g_free (escape);
2414 if (media)
2415 media->unref ();
2417 return true;
2420 MediaResult
2421 PlaylistParser::Parse ()
2423 bool result;
2424 gint64 last_available_pos;
2425 gint64 size;
2427 LOG_PLAYLIST ("PlaylistParser::Parse ()\n");
2429 do {
2430 // Don't try to parse anything until we have all the data.
2431 if (internal != NULL)
2432 internal->reparse = false;
2433 size = source->GetSize ();
2434 last_available_pos = source->GetLastAvailablePosition ();
2435 if (size != -1 && last_available_pos != -1 && size != last_available_pos)
2436 return MEDIA_NOT_ENOUGH_DATA;
2438 if (this->IsASX2 (source)) {
2439 /* Parse as a asx2 mms file */
2440 Setup (XML_TYPE_NONE);
2441 result = this->ParseASX2 ();
2442 } else if (this->IsASX3 (source)) {
2443 Setup (XML_TYPE_ASX3);
2444 result = this->ParseASX3 ();
2445 } else {
2446 result = false;
2448 } while (result && internal->reparse);
2450 return result ? MEDIA_SUCCESS : MEDIA_FAIL;
2453 bool
2454 PlaylistParser::ParseASX3 ()
2456 int bytes_read;
2457 void *buffer;
2459 // asx documents don't tend to be very big, so there's no need for a big buffer
2460 const unsigned int BUFFER_SIZE = 1024;
2462 for (;;) {
2463 buffer = XML_GetBuffer(internal->parser, BUFFER_SIZE);
2464 if (buffer == NULL) {
2465 fprintf (stderr, "Could not allocate memory for asx document parsing.\n");
2466 return false;
2469 bytes_read = source->ReadSome (buffer, BUFFER_SIZE / 2);
2470 if (bytes_read < 0) {
2471 fprintf (stderr, "Could not read asx document for parsing.\n");
2472 return false;
2475 if (bytes_read > 0) {
2477 * ASX files are ANSI (actually whatever encoding is in use on the machine that generated the ASX file...)
2478 * try to convert from ANSI (windows 1252 codepage) to utf8
2482 gsize bytes_converted;
2483 gsize bytes_written;
2484 GError *err = NULL;
2485 char *utf8 = g_convert ((char *) buffer, bytes_read, "UTF-8", "CP1252", &bytes_converted, &bytes_written, &err);
2487 if (utf8 != NULL) {
2488 if (bytes_written >= BUFFER_SIZE) {
2489 fprintf (stderr, "Moonlight: Conversion from ANSI playlist file to UTF8 to more than doubled the required space, this is not normal (file will not be parsed).\n");
2490 return false;
2491 } else {
2492 memcpy (buffer, utf8, bytes_written);
2493 bytes_read = bytes_written;
2495 } else {
2496 g_error_free (err);
2497 fprintf (stderr, "Moonlight: Could not convert ASX3 playlist file from ANSI encoding to UTF8. Will try to parse ASX3 playlist as if it was UTF8.\n");
2501 * do & => &amp; fixups
2504 int size_left = BUFFER_SIZE - bytes_read;
2505 int pos = 0;
2506 char *cbuf = (char *) buffer;
2507 while (size_left > 4 && pos < bytes_read) {
2508 if (cbuf [pos] == '&') {
2509 memmove (cbuf + 5 + pos, cbuf + 1 + pos, bytes_read - pos + 1);
2510 cbuf [pos + 1] = 'a';
2511 cbuf [pos + 2] = 'm';
2512 cbuf [pos + 3] = 'p';
2513 cbuf [pos + 4] = ';';
2514 pos += 5;
2515 size_left -= 4;
2516 bytes_read += 4;
2517 } else {
2518 pos++;
2523 if (!XML_ParseBuffer (internal->parser, bytes_read, bytes_read == 0)) {
2524 if (error_args != NULL)
2525 return false;
2527 switch (XML_GetErrorCode (internal->parser)) {
2528 case XML_ERROR_NO_ELEMENTS:
2529 ParsingError (new ErrorEventArgs (MediaError,
2530 MoonError (MoonError::EXCEPTION, 7000, "unexpected end of input")));
2531 return false;
2532 case XML_ERROR_DUPLICATE_ATTRIBUTE:
2533 ParsingError (new ErrorEventArgs (MediaError,
2534 MoonError (MoonError::EXCEPTION, 7031, "wfc: unique attribute spec")));
2535 return false;
2536 case XML_ERROR_INVALID_TOKEN:
2537 // save error args in case the error fixing fails (in which case we want this error, not the error the error fixing caused)
2538 error_args = new ErrorEventArgs (MediaError,
2539 MoonError (MoonError::EXCEPTION, 7007, "quote expected"));
2540 if (TryFixError ((gint8 *) buffer, bytes_read))
2541 return true;
2542 // fall through
2543 default:
2544 char *msg = g_strdup_printf ("%s %d (%d, %d)",
2545 XML_ErrorString (XML_GetErrorCode (internal->parser)), (int) XML_GetErrorCode (internal->parser),
2546 (int) XML_GetCurrentLineNumber (internal->parser), (int) XML_GetCurrentColumnNumber (internal->parser));
2547 ParsingError (new ErrorEventArgs (MediaError,
2548 MoonError (MoonError::EXCEPTION, 3000, msg)));
2549 g_free (msg);
2550 return false;
2554 if (bytes_read == 0)
2555 break;
2557 internal->bytes_read += bytes_read;
2560 return playlist != NULL;
2563 PlaylistEntry *
2564 PlaylistParser::GetCurrentContent ()
2566 if (current_entry != NULL)
2567 return current_entry;
2569 return playlist;
2572 PlaylistEntry *
2573 PlaylistParser::GetCurrentEntry ()
2575 return current_entry;
2578 void
2579 PlaylistParser::EndEntry ()
2581 this->current_entry = NULL;
2584 void
2585 PlaylistParser::PushCurrentKind (PlaylistKind::Kind kind)
2587 kind_stack->Append (new KindNode (kind));
2588 LOG_PLAYLIST ("PlaylistParser::Push (%d)\n", kind);
2591 void
2592 PlaylistParser::PopCurrentKind ()
2594 LOG_PLAYLIST ("PlaylistParser::PopCurrentKind (), current: %d\n", ((KindNode *)kind_stack->Last ())->kind);
2595 kind_stack->Remove (kind_stack->Last ());
2598 PlaylistKind::Kind
2599 PlaylistParser::GetCurrentKind ()
2601 KindNode *node = (KindNode *) kind_stack->Last ();
2602 return node->kind;
2605 PlaylistKind::Kind
2606 PlaylistParser::GetParentKind ()
2608 KindNode *node = (KindNode *) kind_stack->Last ()->prev;
2609 return node->kind;
2612 bool
2613 PlaylistParser::AssertParentKind (int kind)
2615 LOG_PLAYLIST ("PlaylistParser::AssertParentKind (%d), GetParentKind: %d, result: %d\n", kind, GetParentKind (), GetParentKind () & kind);
2617 if (GetParentKind () & kind)
2618 return true;
2620 ParsingError (new ErrorEventArgs (MediaError,
2621 MoonError (MoonError::EXCEPTION, 3008, "ASX parse error")));
2623 return false;
2626 void
2627 PlaylistParser::ParsingError (ErrorEventArgs *args)
2629 LOG_PLAYLIST ("PlaylistParser::ParsingError (%s)\n", args->GetErrorMessage());
2631 XML_StopParser (internal->parser, false);
2632 if (error_args) {
2633 if (args)
2634 args->unref ();
2635 return; // don't overwrite any previous errors.
2637 error_args = args; // don't ref, this method is called like this: ParsingError (new ErrorEventArgs (...));, so the caller gives us the ref he has
2641 PlaylistKind PlaylistParser::playlist_kinds [] = {
2642 /* ASX3 */
2643 PlaylistKind ("ABSTRACT", PlaylistKind::Abstract),
2644 PlaylistKind ("ASX", PlaylistKind::Asx),
2645 PlaylistKind ("ROOT", PlaylistKind::Root),
2646 PlaylistKind ("AUTHOR", PlaylistKind::Author),
2647 PlaylistKind ("BANNER", PlaylistKind::Banner),
2648 PlaylistKind ("BASE", PlaylistKind::Base),
2649 PlaylistKind ("COPYRIGHT", PlaylistKind::Copyright),
2650 PlaylistKind ("DURATION", PlaylistKind::Duration),
2651 PlaylistKind ("ENTRY", PlaylistKind::Entry),
2652 PlaylistKind ("ENTRYREF", PlaylistKind::EntryRef),
2653 PlaylistKind ("LOGURL", PlaylistKind::LogUrl),
2654 PlaylistKind ("MOREINFO", PlaylistKind::MoreInfo),
2655 PlaylistKind ("REF", PlaylistKind::Ref),
2656 PlaylistKind ("STARTTIME", PlaylistKind::StartTime),
2657 PlaylistKind ("TITLE", PlaylistKind::Title),
2658 PlaylistKind ("STARTMARKER", PlaylistKind::StartMarker),
2659 PlaylistKind ("REPEAT", PlaylistKind::Repeat),
2660 PlaylistKind ("ENDMARKER", PlaylistKind::EndMarker),
2661 PlaylistKind ("PARAM", PlaylistKind::Param),
2662 PlaylistKind ("EVENT", PlaylistKind::Event),
2664 PlaylistKind (NULL, PlaylistKind::Unknown)
2667 PlaylistKind::Kind
2668 PlaylistParser::StringToKind (const char *str)
2670 PlaylistKind::Kind kind = PlaylistKind::Unknown;
2672 for (int i = 0; playlist_kinds [i].str != NULL; i++) {
2673 if (str_match (str, playlist_kinds [i].str)) {
2674 kind = playlist_kinds [i].kind;
2675 break;
2679 LOG_PLAYLIST ("PlaylistParser::StringToKind ('%s') = %d\n", str, kind);
2681 return kind;
2684 const char *
2685 PlaylistParser::KindToString (PlaylistKind::Kind kind)
2687 const char *result = NULL;
2689 for (int i = 0; playlist_kinds [i].str != NULL; i++) {
2690 if (playlist_kinds [i].kind == kind) {
2691 result = playlist_kinds [i].str;
2692 break;
2696 LOG_PLAYLIST ("PlaylistParser::KindToString (%d) = '%s'\n", kind, result);
2698 return result;