2009-11-12 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / pipeline-asf.cpp
blob8acdb366a6a62a8044fc42da734ce462cfebd91b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * pipeline-asf.cpp: ASF related parts of the pipeline
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.
15 #include <config.h>
17 #include "pipeline-asf.h"
18 #include "mms-downloader.h"
19 #include "debug.h"
20 #include "playlist.h"
22 #define VIDEO_BITRATE_PERCENTAGE 75
23 #define AUDIO_BITRATE_PERCENTAGE 25
26 * ASFDemuxer
29 ASFDemuxer::ASFDemuxer (Media *media, IMediaSource *source) : IMediaDemuxer (Type::ASFDEMUXER, media, source)
31 stream_to_asf_index = NULL;
32 reader = NULL;
33 parser = NULL;
36 void
37 ASFDemuxer::Dispose ()
39 g_free (stream_to_asf_index);
40 stream_to_asf_index = NULL;
42 delete reader;
43 reader = NULL;
45 if (parser) {
46 parser->Dispose ();
47 parser->unref ();
48 parser = NULL;
51 IMediaDemuxer::Dispose ();
54 void
55 ASFDemuxer::UpdateSelected (IMediaStream *stream)
57 if (reader)
58 reader->SelectStream (stream_to_asf_index [stream->index], stream->GetSelected ());
60 IMediaDemuxer::UpdateSelected (stream);
63 void
64 ASFDemuxer::SeekAsyncInternal (guint64 pts)
66 MediaResult result;
68 LOG_PIPELINE ("ASFDemuxer::Seek (%" G_GUINT64_FORMAT ")\n", pts);
70 g_return_if_fail (reader != NULL);
71 g_return_if_fail (Media::InMediaThread ());
73 result = reader->Seek (pts);
75 if (MEDIA_SUCCEEDED (result)) {
76 LOG_PIPELINE ("ASFDemuxer:Seek (%" G_GUINT64_FORMAT "): seek completed, reporting it\n", pts);
77 ReportSeekCompleted (pts);
78 } else if (result == MEDIA_NOT_ENOUGH_DATA) {
79 LOG_PIPELINE ("ASFDemuxer:Seek (%" G_GUINT64_FORMAT "): not enough data\n", pts);
80 EnqueueSeek ();
81 } else {
82 ReportErrorOccurred (result);
86 void
87 ASFDemuxer::SwitchMediaStreamAsyncInternal (IMediaStream *stream)
89 LOG_PIPELINE ("ASFDemuxer::SwitchMediaStreamAsyncInternal (%p). TODO.\n", stream);
92 void
93 ASFDemuxer::ReadMarkers ()
96 We can get markers from several places:
97 - The header of the file, read before starting to play
98 - As a SCRIPT_COMMAND
99 - As a MARKER
100 They are both treated the same way, added into the timeline marker collection when the media is loaded.
101 - As data in the file (a separate stream whose type is ASF_COMMAND_MEDIA)
102 These markers show up while playing the file, and they don't show up in the timeline marker collection,
103 they only get to raise the MarkerReached event.
104 currently the demuxer will call the streamed_marker_callback when it encounters any of these.
107 // Hookup to the marker (ASF_COMMAND_MEDIA) stream
108 MediaMarker *marker;
109 Media *media = GetMediaReffed ();
111 g_return_if_fail (media != NULL);
113 // Read the markers (if any)
114 List *markers = media->GetMarkers ();
115 const char *type;
116 guint64 pts;
117 guint64 preroll_pts = MilliSeconds_ToPts (parser->GetFileProperties ()->preroll);
118 char *text;
119 int i = -1;
121 // Read the SCRIPT COMMANDs
122 char **command_types = NULL;
123 asf_script_command_entry **commands = NULL;
124 asf_script_command *command = parser->script_command;
126 if (command != NULL) {
127 commands = command->get_commands (parser, &command_types);
129 if (command_types == NULL) {
130 //printf ("MediaElement::ReadASFMarkers (): No command types.\n");
131 goto cleanup;
135 if (commands != NULL) {
136 for (i = 0; commands[i]; i++) {
137 asf_script_command_entry *entry = commands [i];
139 text = entry->get_name ();
140 pts = MilliSeconds_ToPts (entry->pts) - preroll_pts;
142 if (entry->type_index + 1 <= command->command_type_count)
143 type = command_types [entry->type_index];
144 else
145 type = "";
147 marker = new MediaMarker (type, text, pts);
148 markers->Append (new MediaMarker::Node (marker));
149 marker->unref ();
151 //printf ("MediaElement::ReadMarkers () Added script command at %" G_GUINT64_FORMAT " (text: %s, type: %s)\n", pts, text, type);
153 g_free (text);
157 // Read the MARKERs
158 asf_marker *asf_marker;
159 const asf_marker_entry* marker_entry;
161 asf_marker = parser->marker;
162 if (asf_marker != NULL) {
163 for (i = 0; i < (int) asf_marker->marker_count; i++) {
164 marker_entry = asf_marker->get_entry (i);
165 text = marker_entry->get_marker_description ();
167 pts = marker_entry->pts - preroll_pts;
169 marker = new MediaMarker ("Name", text, pts);
170 markers->Append (new MediaMarker::Node (marker));
171 marker->unref ();
173 //printf ("MediaElement::ReadMarkers () Added marker at %" G_GUINT64_FORMAT " (text: %s, type: %s)\n", pts, text, "Name");
175 g_free (text);
180 cleanup:
181 g_strfreev (command_types);
182 g_free (commands);
183 media->unref ();
186 void
187 ASFDemuxer::SetParser (ASFParser *parser)
189 if (this->parser)
190 this->parser->unref ();
191 this->parser = parser;
192 if (this->parser) {
193 this->parser->ref ();
194 this->parser->SetSource (source);
198 void
199 ASFDemuxer::OpenDemuxerAsyncInternal ()
201 MediaResult result;
203 LOG_PIPELINE ("ASFDemuxer::OpenDemuxerAsyncInternal ()\n");
205 result = Open ();
207 if (MEDIA_SUCCEEDED (result)) {
208 ReportOpenDemuxerCompleted ();
209 } else if (result == MEDIA_NOT_ENOUGH_DATA) {
210 EnqueueOpen ();
211 } else {
212 ReportErrorOccurred (result);
216 MediaResult
217 ASFDemuxer::Open ()
219 MediaResult result = MEDIA_SUCCESS;
220 ASFParser *asf_parser = NULL;
221 gint32 *stream_to_asf_index = NULL;
222 IMediaStream **streams = NULL;
223 Media *media = GetMediaReffed ();
224 int current_stream = 1;
225 int stream_count = 0;
226 int count;
228 g_return_val_if_fail (media != NULL, MEDIA_FAIL);
230 if (parser != NULL) {
231 asf_parser = parser;
232 } else {
233 asf_parser = new ASFParser (source, media);
236 LOG_PIPELINE_ASF ("ASFDemuxer::ReadHeader ().\n");
238 result = asf_parser->ReadHeader ();
239 if (!MEDIA_SUCCEEDED (result)) {
240 if (result == MEDIA_NOT_ENOUGH_DATA) {
241 LOG_PIPELINE_ASF ("ASFDemuxer::ReadHeader (): ReadHeader failed due to not enough data being available.\n");
242 } else {
243 Media::Warning (MEDIA_INVALID_MEDIA, "asf_parser->ReadHeader () failed:");
244 Media::Warning (MEDIA_FAIL, "%s", asf_parser->GetLastErrorStr ());
246 goto failure;
249 // Count the number of streams
250 for (int i = 1; i <= 127; i++) {
251 if (asf_parser->IsValidStream (i))
252 stream_count++;
255 current_stream = 1;
256 streams = (IMediaStream **) g_malloc0 (sizeof (IMediaStream *) * (stream_count + 1)); // End with a NULL element.
257 stream_to_asf_index = (gint32 *) g_malloc0 (sizeof (gint32) * (stream_count + 1));
259 // keep count as a separate local since we can change its value (e.g. bad stream)
260 count = stream_count;
261 // Loop through all the streams and set stream-specific data
262 for (int i = 0; i < count; i++) {
263 while (current_stream <= 127 && !asf_parser->IsValidStream (current_stream))
264 current_stream++;
266 if (current_stream > 127) {
267 result = MEDIA_INVALID_STREAM;
268 Media::Warning (result, "Couldn't find all the claimed streams in the file.");
269 goto failure;
272 const asf_stream_properties* stream_properties = asf_parser->GetStream (current_stream);
273 IMediaStream* stream = NULL;
275 if (stream_properties == NULL) {
276 result = MEDIA_INVALID_STREAM;
277 Media::Warning (result, "Couldn't find all the claimed streams in the file.");
278 goto failure;
281 if (stream_properties->is_audio ()) {
282 AudioStream* audio = new AudioStream (media);
284 stream = audio;
286 const WAVEFORMATEX* wave = stream_properties->get_audio_data ();
287 if (wave == NULL) {
288 result = MEDIA_INVALID_STREAM;
289 Media::Warning (result, "Couldn't find audio data in the file.");
290 goto failure;
293 const WAVEFORMATEXTENSIBLE* wave_ex = wave->get_wave_format_extensible ();
294 int data_size = stream_properties->size - sizeof (asf_stream_properties) - sizeof (WAVEFORMATEX);
296 audio->SetChannels (wave->channels);
297 audio->SetSampleRate (wave->samples_per_second);
298 audio->SetBitRate (wave->bytes_per_second * 8);
299 audio->SetBlockAlign (wave->block_alignment);
300 audio->SetBitsPerSample (wave->bits_per_sample);
301 audio->SetExtraData (NULL);
302 audio->SetExtraDataSize (data_size > wave->codec_specific_data_size ? wave->codec_specific_data_size : data_size);
303 audio->SetCodecId (wave->codec_id);
305 if (wave_ex != NULL) {
306 audio->SetBitsPerSample (wave_ex->Samples.valid_bits_per_sample);
307 audio->SetExtraDataSize (audio->GetExtraDataSize () - (sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX)));
308 audio->SetCodecId (*((guint32*) &wave_ex->sub_format));
311 // Fill in any extra codec data
312 if (audio->GetExtraDataSize () > 0) {
313 audio->SetExtraData (g_malloc0 (audio->GetExtraDataSize ()));
314 char* src = ((char*) wave) + (wave_ex ? sizeof (WAVEFORMATEX) : sizeof (WAVEFORMATEX));
315 memcpy (audio->GetExtraData (), src, audio->GetExtraDataSize ());
317 } else if (stream_properties->is_video ()) {
318 VideoStream* video = new VideoStream (media);
319 stream = video;
321 const asf_video_stream_data* video_data = stream_properties->get_video_data ();
322 const BITMAPINFOHEADER* bmp;
323 const asf_extended_stream_properties* aesp;
325 if (video_data != NULL) {
326 bmp = video_data->get_bitmap_info_header ();
327 aesp = asf_parser->GetExtendedStream (current_stream);
328 if (bmp != NULL) {
329 video->width = bmp->image_width;
330 video->height = bmp->image_height;
332 // note: both height and width are unsigned
333 if ((video->height > MAX_VIDEO_HEIGHT) || (video->width > MAX_VIDEO_WIDTH)) {
334 result = MEDIA_INVALID_STREAM;
335 Media::Warning (result,
336 "Video stream size (width: %d, height: %d) outside limits (%d, %d)",
337 video->height, video->width, MAX_VIDEO_HEIGHT, MAX_VIDEO_WIDTH);
338 goto failure;
341 video->bits_per_sample = bmp->bits_per_pixel;
342 video->codec_id = bmp->compression_id;
343 video->extra_data_size = bmp->get_extra_data_size ();
344 if (video->extra_data_size > 0) {
345 video->extra_data = g_malloc0 (video->extra_data_size);
346 memcpy (video->extra_data, bmp->get_extra_data (), video->extra_data_size);
347 } else {
348 video->extra_data = NULL;
351 if (aesp != NULL) {
352 video->bit_rate = aesp->data_bitrate;
353 video->pts_per_frame = aesp->average_time_per_frame;
354 } else {
355 video->bit_rate = video->width*video->height;
356 video->pts_per_frame = 0;
359 } else if (stream_properties->is_command ()) {
360 MarkerStream* marker = new MarkerStream (media);
361 stream = marker;
362 stream->codec = g_strdup ("asf-marker");
363 } else {
364 // Unknown stream, don't include it in the count since it's NULL
365 stream_count--;
366 // also adjust indexes so we don't create a hole in the streams array
367 count--;
368 i--;
371 if (stream != NULL) {
372 if (stream_properties->is_video () || stream_properties->is_audio ()) {
373 switch (stream->codec_id) {
374 case CODEC_WMV1: stream->codec = g_strdup ("wmv1"); break;
375 case CODEC_WMV2: stream->codec = g_strdup ("wmv2"); break;
376 case CODEC_WMV3: stream->codec = g_strdup ("wmv3"); break;
377 case CODEC_WMVA: stream->codec = g_strdup ("wmva"); break;
378 case CODEC_WVC1: stream->codec = g_strdup ("vc1"); break;
379 case CODEC_MP3: stream->codec = g_strdup ("mp3"); break;
380 case CODEC_WMAV1: stream->codec = g_strdup ("wmav1"); break;
381 case CODEC_WMAV2: stream->codec = g_strdup ("wmav2"); break;
382 case CODEC_WMAV3: stream->codec = g_strdup ("wmav3"); break;
383 default:
384 char a = ((stream->codec_id & 0x000000FF));
385 char b = ((stream->codec_id & 0x0000FF00) >> 8);
386 char c = ((stream->codec_id & 0x00FF0000) >> 16);
387 char d = ((stream->codec_id & 0xFF000000) >> 24);
388 stream->codec = g_strdup_printf ("unknown (%c%c%c%c)", a ? a : ' ', b ? b : ' ', c ? c : ' ', d ? d : ' ');
389 break;
392 streams [i] = stream;
393 stream->index = i;
394 if (!asf_parser->file_properties->is_broadcast ()) {
395 stream->duration = asf_parser->file_properties->play_duration - MilliSeconds_ToPts (asf_parser->file_properties->preroll);
397 stream_to_asf_index [i] = current_stream;
400 current_stream++;
404 if (!MEDIA_SUCCEEDED (result)) {
405 goto failure;
408 SetStreams (streams, stream_count);
410 for (int i = 0; i < stream_count; i++)
411 streams [i]->unref ();
413 this->stream_to_asf_index = stream_to_asf_index;
414 this->parser = asf_parser;
416 reader = new ASFReader (parser, this);
418 ReadMarkers ();
420 media->unref ();
422 return result;
424 failure:
425 asf_parser->unref ();
426 asf_parser = NULL;
428 g_free (stream_to_asf_index);
429 stream_to_asf_index = NULL;
431 if (streams != NULL) {
432 for (int i = 0; i < stream_count; i++) {
433 if (streams [i] != NULL) {
434 streams [i]->unref ();
435 streams [i] = NULL;
438 g_free (streams);
439 streams = NULL;
442 media->unref ();
444 return result;
447 IMediaStream *
448 ASFDemuxer::GetStreamOfASFIndex (gint32 asf_index)
450 for (gint32 i = 0; i < GetStreamCount (); i++) {
451 if (stream_to_asf_index [i] == asf_index)
452 return GetStream (i);
454 return NULL;
457 MediaResult
458 ASFDemuxer::GetFrameCallback (MediaClosure *c)
460 MediaGetFrameClosure *closure = (MediaGetFrameClosure *) c;
461 ASFDemuxer *demuxer = (ASFDemuxer *) closure->GetDemuxer ();
462 demuxer->GetFrameAsyncInternal (closure->GetStream ());
464 return MEDIA_SUCCESS;
467 void
468 ASFDemuxer::GetFrameAsyncInternal (IMediaStream *stream)
470 //printf ("ASFDemuxer::ReadFrame (%p).\n", frame);
471 ASFFrameReader *reader = NULL;
472 MediaFrame *frame;
473 MediaResult result;
475 g_return_if_fail (this->reader != NULL);
477 reader = this->reader->GetFrameReader (stream_to_asf_index [stream->index]);
479 g_return_if_fail (reader != NULL);
481 result = reader->Advance ();
483 if (result == MEDIA_NO_MORE_DATA) {
484 ReportGetFrameCompleted (NULL);
485 return;
488 if (result == MEDIA_BUFFER_UNDERFLOW || result == MEDIA_NOT_ENOUGH_DATA) {
489 Media *media = GetMediaReffed ();
490 g_return_if_fail (media != NULL);
491 MediaClosure *closure = new MediaGetFrameClosure (media, GetFrameCallback, this, stream);
492 media->EnqueueWork (closure, false); // TODO: use a timeout here, no need to try again immediately.
493 closure->unref ();
494 media->unref ();
495 return;
498 if (!MEDIA_SUCCEEDED (result)) {
499 ReportErrorOccurred ("Error while advancing to the next frame (%d)");
500 return;
503 frame = new MediaFrame (stream);
504 frame->pts = reader->Pts ();
505 //frame->duration = reader->Duration ();
506 if (reader->IsKeyFrame ())
507 frame->AddState (MediaFrameKeyFrame);
508 frame->buflen = reader->Size ();
509 frame->buffer = (guint8 *) g_try_malloc (frame->buflen + frame->stream->min_padding);
511 if (frame->buffer == NULL) {
512 ReportErrorOccurred ( "Could not allocate memory for next frame.");
513 return;
516 //printf ("ASFDemuxer::ReadFrame (%p), min_padding = %i\n", frame, frame->stream->min_padding);
517 if (frame->stream->min_padding > 0)
518 memset (frame->buffer + frame->buflen, 0, frame->stream->min_padding);
520 if (!reader->Write (frame->buffer)) {
521 ReportErrorOccurred ("Error while copying the next frame.");
522 return;
525 frame->AddState (MediaFrameDemuxed);
527 ReportGetFrameCompleted (frame);
529 frame->unref ();
533 * ASFMarkerDecoder
536 ASFMarkerDecoder::ASFMarkerDecoder (Media *media, IMediaStream *stream)
537 : IMediaDecoder (Type::ASFMARKERDECODER, media, stream)
541 void
542 ASFMarkerDecoder::OpenDecoderAsyncInternal ()
544 ReportOpenDecoderCompleted ();
547 void
548 ASFMarkerDecoder::DecodeFrameAsyncInternal (MediaFrame *frame)
550 LOG_PIPELINE_ASF ("ASFMarkerDecoder::DecodeFrame ()\n");
552 MediaResult result;
553 char *text;
554 char *type;
555 gunichar2 *data;
556 gunichar2 *uni_type = NULL;
557 gunichar2 *uni_text = NULL;
558 int text_length = 0;
559 int type_length = 0;
560 guint32 size = 0;
562 if (frame->buflen % 2 != 0 || frame->buflen == 0 || frame->buffer == NULL) {
563 ReportErrorOccurred (MEDIA_CORRUPTED_MEDIA);
564 return;
567 data = (gunichar2 *) frame->buffer;
568 uni_type = data;
569 size = frame->buflen;
571 // the data is two arrays of WCHARs (type and text), null terminated.
572 // loop through the data, counting characters and null characters
573 // there should be at least two null characters.
574 int null_count = 0;
576 for (guint32 i = 0; i < (size / sizeof (gunichar2)); i++) {
577 if (uni_text == NULL) {
578 type_length++;
579 } else {
580 text_length++;
582 if (*(data + i) == 0) {
583 null_count++;
584 if (uni_text == NULL) {
585 uni_text = data + i + 1;
586 } else {
587 break; // Found at least two nulls
592 if (null_count >= 2) {
593 text = wchar_to_utf8 (uni_text, text_length);
594 type = wchar_to_utf8 (uni_type, type_length);
596 LOG_PIPELINE_ASF ("ASFMarkerDecoder::DecodeFrame (): sending script command type: '%s', text: '%s', pts: '%" G_GUINT64_FORMAT "'.\n", type, text, frame->pts);
598 frame->marker = new MediaMarker (type, text, frame->pts);
600 g_free (text);
601 g_free (type);
602 result = MEDIA_SUCCESS;
603 } else {
604 LOG_PIPELINE_ASF ("ASFMarkerDecoder::DecodeFrame (): didn't find 2 null characters in the data.\n");
605 result = MEDIA_CORRUPTED_MEDIA;
608 if (MEDIA_SUCCEEDED (result)) {
609 ReportDecodeFrameCompleted (frame);
610 } else {
611 ReportErrorOccurred (result);
616 * ASFMarkerDecoderInfo
619 IMediaDecoder *
620 ASFMarkerDecoderInfo::Create (Media *media, IMediaStream *stream)
622 return new ASFMarkerDecoder (media, stream);
625 bool
626 ASFMarkerDecoderInfo::Supports (const char *codec)
628 return !strcmp (codec, "asf-marker");
631 const char *
632 ASFMarkerDecoderInfo::GetName ()
634 return "ASFMarkerDecoder";
638 * ASFDemuxerInfo
641 MediaResult
642 ASFDemuxerInfo::Supports (IMediaSource *source)
644 guint8 buffer[16];
645 bool result;
647 LOG_PIPELINE_ASF ("ASFDemuxerInfo::Supports (%p) pos: %" G_GINT64_FORMAT ", avail pos: %" G_GINT64_FORMAT "\n", source, source->GetPosition (), source->GetLastAvailablePosition ());
649 #if DEBUG
650 bool eof = false;
651 if (!source->GetPosition () == 0)
652 fprintf (stderr, "ASFDemuxerInfo::Supports (%p): Trying to check if a media is supported, but the media isn't at position 0 (it's at position %" G_GINT64_FORMAT ")\n", source, source->GetPosition ());
653 if (!source->IsPositionAvailable (16, &eof)) // This shouldn't happen, we should have at least 1024 bytes (or eof).
654 fprintf (stderr, "ASFDemuxerInfo::Supports (%p): Not enough data! eof: %i\n", source, eof);
655 #endif
657 if (!source->Peek (buffer, 16)) {
658 fprintf (stderr, "ASFDemuxerInfo::Supports (%p): Peek failed.\n", source);
659 return MEDIA_FAIL;
662 result = asf_guid_compare (&asf_guids_header, (asf_guid *) buffer);
664 //printf ("ASFDemuxerInfo::Supports (%p): probing result: %s %s\n", source, source->ToString (),
665 // result ? "true" : "false");
667 return result ? MEDIA_SUCCESS : MEDIA_FAIL;
670 IMediaDemuxer *
671 ASFDemuxerInfo::Create (Media *media, IMediaSource *source)
673 return new ASFDemuxer (media, source);
677 * MmsSource
680 MmsSource::MmsSource (Media *media, Downloader *downloader)
681 : IMediaSource (Type::MMSSOURCE, media)
683 finished = false;
684 write_count = 0;
685 this->downloader = NULL;
686 current = NULL;
687 demuxer = NULL;
689 g_return_if_fail (downloader != NULL);
690 g_return_if_fail (downloader->GetInternalDownloader () != NULL);
691 g_return_if_fail (downloader->GetInternalDownloader ()->GetObjectType () == Type::MMSDOWNLOADER);
693 this->downloader = downloader;
694 this->downloader->ref ();
696 ReportStreamChange (0); // create the initial MmsPlaylistEntry
699 void
700 MmsSource::Dispose ()
702 // thread safe method
704 MmsPlaylistEntry *entry;
705 IMediaDemuxer *demux;
706 Downloader *dl;
708 // don't lock during unref, only while nulling out the local field
709 Lock ();
710 entry = this->current;
711 this->current = NULL;
712 dl = this->downloader;
713 this->downloader = NULL;
714 demux = this->demuxer;
715 this->demuxer = NULL;
716 Unlock ();
718 if (dl) {
719 dl->RemoveAllHandlers (this);
720 dl->unref ();
723 if (entry)
724 entry->unref ();
726 if (demux)
727 demux->unref ();
729 IMediaSource::Dispose ();
732 MediaResult
733 MmsSource::Initialize ()
735 Downloader *dl;
736 MmsDownloader *mms_dl;
738 VERIFY_MAIN_THREAD;
740 dl = GetDownloaderReffed ();
742 g_return_val_if_fail (dl != NULL, MEDIA_FAIL);
743 g_return_val_if_fail (!dl->Started (), MEDIA_FAIL);
745 // We must call MmsDownloader::SetSource before the downloader
746 // has actually received any data. Here we rely on the fact that
747 // firefox needs a tick before returning any data.
748 mms_dl = GetMmsDownloader (dl);
749 if (mms_dl != NULL) {
750 mms_dl->SetSource (this);
751 } else {
752 printf ("MmsSource::Initialize (): Could not get the MmsDownloader. Media won't play.\n");
755 dl->AddHandler (Downloader::DownloadFailedEvent, DownloadFailedCallback, this);
756 dl->AddHandler (Downloader::CompletedEvent, DownloadCompleteCallback, this);
757 dl->Send ();
759 dl->unref ();
761 return MEDIA_SUCCESS;
764 MmsDemuxer *
765 MmsSource::GetDemuxerReffed ()
767 MmsDemuxer *result;
768 Lock ();
769 result = demuxer;
770 if (result)
771 result->ref ();
772 Unlock ();
773 return result;
776 Downloader *
777 MmsSource::GetDownloaderReffed ()
779 Downloader *result;
780 Lock ();
781 result = downloader;
782 if (downloader)
783 downloader->ref ();
784 Unlock ();
785 return result;
788 MmsPlaylistEntry *
789 MmsSource::GetCurrentReffed ()
791 MmsPlaylistEntry *result;
793 // Thread safe
795 Lock ();
796 result = current;
797 if (result)
798 result->ref ();
799 Unlock ();
801 return result;
804 void
805 MmsSource::ReportDownloadFailure ()
807 Media *media;
809 LOG_MMS ("MmsSource::ReportDownloadFailure ()\n");
810 VERIFY_MAIN_THREAD;
812 media = GetMediaReffed ();
814 g_return_if_fail (media != NULL);
816 media->ReportErrorOccurred ("MmsDownloader failed");
817 media->unref ();
820 void
821 MmsSource::ReportStreamChange (gint32 reason)
823 Media *media;
824 PlaylistRoot *root;
825 Media *entry_media;
827 LOG_MMS ("MmsSource::ReportStreamChange (reason: %i)\n", reason);
829 VERIFY_MAIN_THREAD;
831 media = GetMediaReffed ();
833 g_return_if_fail (media != NULL);
835 root = media->GetPlaylistRoot ();
837 g_return_if_fail (root != NULL);
839 Lock ();
840 if (current != NULL) {
841 current->NotifyFinished ();
842 current->unref ();
845 entry_media = new Media (root);
846 current = new MmsPlaylistEntry (entry_media, this);
847 entry_media->unref ();
848 Unlock ();
850 media->unref ();
853 void
854 MmsSource::SetMmsMetadata (const char *playlist_gen_id, const char *broadcast_id, HttpStreamingFeatures features)
856 MmsPlaylistEntry *entry;
858 LOG_MMS ("MmsSource::SetMmsMetadata ('%s', '%s', %i)\n", playlist_gen_id, broadcast_id, (int) features);
860 VERIFY_MAIN_THREAD;
862 entry = GetCurrentReffed ();
864 g_return_if_fail (entry != NULL);
866 entry->SetPlaylistGenId (playlist_gen_id);
867 entry->SetBroadcastId (broadcast_id);
868 entry->SetHttpStreamingFeatures (features);
870 entry->unref ();
873 void
874 MmsSource::DownloadFailedHandler (Downloader *dl, EventArgs *args)
876 Media *media = GetMediaReffed ();
877 ErrorEventArgs *eea;
878 VERIFY_MAIN_THREAD;
880 g_return_if_fail (media != NULL);
881 eea = new ErrorEventArgs (MediaError, MoonError (MoonError::EXCEPTION, 4001, "AG_E_NETWORK_ERROR"));
882 media->RetryHttp (eea);
883 media->unref ();
884 eea->unref ();
887 void
888 MmsSource::DownloadCompleteHandler (Downloader *dl, EventArgs *args)
890 VERIFY_MAIN_THREAD;
893 void
894 MmsSource::NotifyFinished (guint32 reason)
896 VERIFY_MAIN_THREAD;
898 LOG_MMS ("MmsSource::NotifyFinished (%i)\n", reason);
900 if (reason == 0) {
901 // The server has finished streaming and no more
902 // Data packets will be transmitted until the next Play request
903 finished = true;
904 } else if (reason == 1) {
905 // The server has finished streaming the current playlist entry. Other playlist
906 // entries still remain to be streamed. The server will transmit a stream change packet
907 // when it switches to the next entry.
908 MmsPlaylistEntry *entry = GetCurrentReffed ();
909 entry->NotifyFinished ();
910 entry->unref ();
911 } else {
912 // ?
916 MediaResult
917 MmsSource::SeekToPts (guint64 pts)
919 // thread safe
920 MediaResult result = true;
921 Downloader *dl;
922 MmsDownloader *mms_dl;
924 LOG_PIPELINE_ASF ("MmsSource::SeekToPts (%" G_GUINT64_FORMAT ")\n", pts);
926 dl = GetDownloaderReffed ();
928 g_return_val_if_fail (dl != NULL, MEDIA_FAIL);
930 mms_dl = GetMmsDownloader (dl);
932 if (mms_dl) {
933 mms_dl->SetRequestedPts (pts);
934 finished = false;
935 result = MEDIA_SUCCESS;
936 } else {
937 result = MEDIA_FAIL;
940 dl->unref ();
942 return result;
945 MmsDownloader *
946 MmsSource::GetMmsDownloader (Downloader *dl)
948 InternalDownloader *idl;
950 g_return_val_if_fail (dl != NULL, NULL);
952 idl = dl->GetInternalDownloader ();
954 if (idl == NULL)
955 return NULL;
957 if (idl->GetObjectType () != Type::MMSDOWNLOADER)
958 return NULL;
960 return (MmsDownloader *) idl;
963 IMediaDemuxer *
964 MmsSource::CreateDemuxer (Media *media)
966 // thread safe
967 MmsDemuxer *result = NULL;
969 g_return_val_if_fail (demuxer == NULL, NULL);
971 Lock ();
972 if (demuxer == NULL) {
973 result = new MmsDemuxer (media, this);
974 demuxer = result;
975 demuxer->ref ();
977 Unlock ();
979 return result;
982 void
983 MmsSource::WritePacket (void *buf, gint32 n)
985 MmsPlaylistEntry *entry;
987 VERIFY_MAIN_THREAD;
989 entry = GetCurrentReffed ();
991 g_return_if_fail (entry != NULL);
993 entry->WritePacket (buf, n);
994 entry->unref ();
997 ASFPacket *
998 MmsSource::Pop ()
1000 MmsPlaylistEntry *entry;
1001 ASFPacket *result;
1003 entry = GetCurrentReffed ();
1005 g_return_val_if_fail (entry != NULL, NULL);
1007 result = entry->Pop ();
1009 entry->unref ();
1011 return result;
1014 bool
1015 MmsSource::Eof ()
1017 // thread safe
1018 MmsPlaylistEntry *entry;
1019 bool result;
1021 if (!finished)
1022 return false;
1024 entry = GetCurrentReffed ();
1026 if (entry == NULL) {
1027 result = true;
1028 } else {
1029 result = entry->Eof ();
1030 entry->unref ();
1033 return result;
1037 * MmsPlaylistEntry
1040 MmsPlaylistEntry::MmsPlaylistEntry (Media *media, MmsSource *source)
1041 : IMediaSource (Type::MMSPLAYLISTENTRY, media)
1043 finished = false;
1044 parent = source;
1045 parser = NULL;
1046 write_count = 0;
1047 demuxer = NULL;
1048 playlist_gen_id = NULL;
1049 broadcast_id = NULL;
1050 features = HttpStreamingFeaturesNone;
1052 g_return_if_fail (parent != NULL);
1053 parent->ref ();
1056 MediaResult
1057 MmsPlaylistEntry::Initialize ()
1059 return MEDIA_SUCCESS;
1062 void
1063 MmsPlaylistEntry::Dispose ()
1065 // thread safe
1066 MmsSource *mms_source;
1067 ASFParser *asf_parser;
1068 IMediaDemuxer *demux;
1070 Lock ();
1071 mms_source = this->parent;
1072 this->parent = NULL;
1073 asf_parser = this->parser;
1074 this->parser = NULL;
1075 demux = this->demuxer;
1076 this->demuxer = NULL;
1077 g_free (playlist_gen_id);
1078 playlist_gen_id = NULL;
1079 g_free (broadcast_id);
1080 broadcast_id = NULL;
1081 Unlock ();
1083 if (mms_source != NULL)
1084 mms_source->unref ();
1086 if (asf_parser != NULL)
1087 asf_parser->unref ();
1089 if (demux != NULL)
1090 demux->unref ();
1092 queue.Clear (true);
1093 // This is a bit weird - in certain
1094 // we can end up with a circular dependency between
1095 // Media and MmsPlaylistEntry, where Media::Dispose
1096 // isn't called. So if Media::Dispose hasn't been
1097 // called, do it here, and only do it after our
1098 // instance copy of the media is cleared out to
1099 // prevent infinite loops.
1100 Media *m = GetMediaReffed ();
1102 IMediaSource::Dispose ();
1104 if (m != NULL) {
1105 if (!m->IsDisposed ())
1106 m->Dispose ();
1107 m->unref ();
1111 MediaResult
1112 MmsPlaylistEntry::SeekToPts (guint64 pts)
1114 MmsSource *ms = GetParentReffed ();
1115 if (ms) {
1116 ms->SeekToPts (pts);
1117 ms->unref ();
1118 return MEDIA_SUCCESS;
1119 } else {
1120 fprintf (stderr, "MmsPlaylistEntry::SeekToPts (%" G_GUINT64_FORMAT "): Could not seek to pts, no parent.\n", pts);
1121 return MEDIA_FAIL;
1125 void
1126 MmsPlaylistEntry::NotifyFinished ()
1128 finished = true;
1131 void
1132 MmsPlaylistEntry::GetSelectedStreams (gint64 max_bitrate, gint8 streams [128])
1134 ASFParser *parser;
1135 asf_file_properties *properties;
1136 gint32 audio_bitrates [128];
1137 gint32 video_bitrates [128];
1139 memset (audio_bitrates, 0xff, 128 * 4);
1140 memset (video_bitrates, 0xff, 128 * 4);
1141 memset (streams, 0xff, 128);
1143 parser = GetParserReffed ();
1145 g_return_if_fail (parser != NULL);
1147 properties = parser->GetFileProperties ();
1149 g_return_if_fail (properties != NULL);
1151 for (int i = 1; i < 127; i++) {
1152 int current_stream;
1153 if (!parser->IsValidStream (i)) {
1154 streams [i] = -1; // inexistent
1155 continue;
1157 streams [i] = 0; // disabled
1158 current_stream = i;
1160 const asf_stream_properties *stream_properties = parser->GetStream (current_stream);
1161 const asf_extended_stream_properties *extended_stream_properties = parser->GetExtendedStream (current_stream);
1163 if (stream_properties == NULL) {
1164 printf ("MmsPlaylistEntry::GetSelectedStreams (): stream #%i doesn't have any stream properties.\n", current_stream);
1165 continue;
1168 if (stream_properties->is_audio ()) {
1169 const WAVEFORMATEX* wave = stream_properties->get_audio_data ();
1170 audio_bitrates [current_stream] = wave->bytes_per_second * 8;
1171 } else if (stream_properties->is_video ()) {
1172 int bit_rate = 0;
1173 const asf_video_stream_data* video_data = stream_properties->get_video_data ();
1174 const BITMAPINFOHEADER* bmp;
1176 if (extended_stream_properties != NULL) {
1177 bit_rate = extended_stream_properties->data_bitrate;
1178 } else if (video_data != NULL) {
1179 bmp = video_data->get_bitmap_info_header ();
1180 if (bmp != NULL) {
1181 bit_rate = bmp->image_width*bmp->image_height;
1185 video_bitrates [current_stream] = bit_rate;
1186 } else if (stream_properties->is_command ()) {
1187 // we select all marker streams
1188 streams [current_stream] = 1;
1192 // select the video stream
1193 int video_stream = 0;
1194 int video_rate = 0;
1196 for (int i = 0; i < 128; i++) {
1197 int stream_rate = video_bitrates [i];
1199 if (stream_rate == -1)
1200 continue;
1202 if (video_rate == 0) {
1203 video_rate = stream_rate;
1204 video_stream = i;
1207 if (stream_rate > video_rate && stream_rate < (max_bitrate * VIDEO_BITRATE_PERCENTAGE)) {
1208 video_rate = stream_rate;
1209 video_stream = i;
1212 streams [video_stream] = 1; // selected
1213 LOG_MMS ("MmsPlaylistEntry::GetSelectedStreams (): Selected video stream %i of rate %i\n", video_stream, video_rate);
1216 // select audio stream
1217 int audio_stream = 0;
1218 int audio_rate = 0;
1220 for (int i = 0; i < 128; i++) {
1221 int stream_rate = audio_bitrates [i];
1223 if (stream_rate == -1)
1224 continue;
1226 if (audio_rate == 0) {
1227 audio_rate = stream_rate;
1228 audio_stream = i;
1231 if (stream_rate > audio_rate && stream_rate < (max_bitrate * AUDIO_BITRATE_PERCENTAGE)) {
1232 audio_rate = stream_rate;
1233 audio_stream = i;
1236 streams [audio_stream] = 1; // selected
1237 LOG_MMS ("MmsPlaylistEntry::GetSelectedStreams (): Selected audio stream %i of rate %i\n", audio_stream, audio_rate);
1239 parser->unref ();
1242 bool
1243 MmsPlaylistEntry::IsHeaderParsed ()
1245 bool result;
1246 Lock ();
1247 result = parser != NULL;
1248 Unlock ();
1249 return result;
1252 IMediaDemuxer *
1253 MmsPlaylistEntry::GetDemuxerReffed ()
1255 // thread safe
1256 IMediaDemuxer *result;
1258 Lock ();
1259 result = demuxer;
1260 if (result)
1261 result->ref ();
1262 Unlock ();
1264 return result;
1267 ASFParser *
1268 MmsPlaylistEntry::GetParserReffed ()
1270 // thread safe
1271 ASFParser *result;
1273 Lock ();
1274 result = parser;
1275 if (result)
1276 result->ref ();
1277 Unlock ();
1279 return result;
1282 MmsSource *
1283 MmsPlaylistEntry::GetParentReffed ()
1285 // thread safe
1286 MmsSource *result;
1288 Lock ();
1289 result = parent;
1290 if (result)
1291 result->ref ();
1292 Unlock ();
1294 return result;
1297 IMediaDemuxer *
1298 MmsPlaylistEntry::CreateDemuxer (Media *media)
1300 // thread safe
1301 ASFDemuxer *result;
1302 ASFParser *asf_parser;
1304 asf_parser = GetParserReffed ();
1306 g_return_val_if_fail (media != NULL, NULL);
1307 g_return_val_if_fail (asf_parser != NULL, NULL);
1308 g_return_val_if_fail (demuxer == NULL, NULL);
1310 result = new ASFDemuxer (media, this);
1311 result->SetParser (asf_parser);
1313 Lock ();
1314 if (demuxer != NULL)
1315 demuxer->unref ();
1316 demuxer = result;
1317 demuxer->ref ();
1318 Unlock ();
1320 asf_parser->unref ();
1322 return result;
1325 void
1326 MmsPlaylistEntry::SetPlaylistGenId (const char *value)
1328 // thread safe
1329 Lock ();
1330 g_free (playlist_gen_id);
1331 playlist_gen_id = g_strdup (value);
1332 Unlock ();
1335 char *
1336 MmsPlaylistEntry::GetPlaylistGenId ()
1338 // thread safe
1339 char *result;
1340 Lock ();
1341 result = g_strdup (playlist_gen_id);
1342 Unlock ();
1343 return result;
1346 void
1347 MmsPlaylistEntry::SetBroadcastId (const char *value)
1349 // thread safe
1350 Lock ();
1351 g_free (broadcast_id);
1352 broadcast_id = g_strdup (value);
1353 Unlock ();
1356 char *
1357 MmsPlaylistEntry::GetBroadcastId ()
1359 // thread safe
1360 char *result;
1361 Lock ();
1362 result = g_strdup (broadcast_id);
1363 Unlock ();
1364 return result;
1367 void
1368 MmsPlaylistEntry::SetHttpStreamingFeatures (HttpStreamingFeatures value)
1370 features = value;
1373 HttpStreamingFeatures
1374 MmsPlaylistEntry::GetHttpStreamingFeatures ()
1376 return features;
1379 void
1380 MmsPlaylistEntry::AddEntry ()
1382 VERIFY_MAIN_THREAD;
1384 Media *media = GetMediaReffed ();
1385 Playlist *playlist;
1386 PlaylistEntry *entry;
1387 MmsDemuxer *mms_demuxer = NULL;
1389 g_return_if_fail (media != NULL);
1391 if (parent == NULL)
1392 goto cleanup;
1394 mms_demuxer = parent->GetDemuxerReffed ();
1396 if (mms_demuxer == NULL)
1397 goto cleanup;
1399 playlist = mms_demuxer->GetPlaylist ();
1401 if (playlist == NULL)
1402 goto cleanup;
1404 entry = new PlaylistEntry (playlist);
1405 entry->SetIsLive (features & HttpStreamingBroadcast);
1407 playlist->AddEntry (entry);
1409 entry->InitializeWithSource (this);
1411 cleanup:
1412 if (media)
1413 media->unref ();
1414 if (mms_demuxer)
1415 mms_demuxer->unref ();
1418 MediaResult
1419 MmsPlaylistEntry::ParseHeader (void *buffer, gint32 size)
1421 VERIFY_MAIN_THREAD;
1423 LOG_MMS ("MmsPlaylistEntry::ParseHeader (%p, %i)\n", buffer, size);
1425 MediaResult result;
1426 MemorySource *asf_src = NULL;
1427 Media *media;
1428 ASFParser *asf_parser;
1430 // this method shouldn't get called more than once
1431 g_return_val_if_fail (parser == NULL, MEDIA_FAIL);
1433 media = GetMediaReffed ();
1434 g_return_val_if_fail (media != NULL, MEDIA_FAIL);
1436 media->ReportDownloadProgress (1.0);
1438 asf_src = new MemorySource (media, buffer, size, 0, false);
1439 asf_parser = new ASFParser (asf_src, media);
1440 result = asf_parser->ReadHeader ();
1441 asf_src->unref ();
1442 media->unref ();
1444 if (MEDIA_SUCCEEDED (result)) {
1445 Lock ();
1446 if (parser)
1447 parser->unref ();
1448 parser = asf_parser;
1449 Unlock ();
1450 AddEntry ();
1451 } else {
1452 asf_parser->unref ();
1455 return result;
1458 ASFPacket *
1459 MmsPlaylistEntry::Pop ()
1461 // thread safe
1462 //LOG_MMS ("MmsSource::Pop (), there are %i packets in the queue, of a total of %" G_GINT64_FORMAT " packets written.\n", queue.Length (), write_count);
1464 QueueNode *node;
1465 ASFPacket *result = NULL;
1466 ASFParser *parser;
1468 trynext:
1469 node = (QueueNode *) queue.Pop ();
1471 if (node == NULL) {
1472 LOG_PIPELINE_ASF ("MmsSource::Pop (): No more packets (for now).\n");
1473 return NULL;
1476 parser = GetParserReffed ();
1478 if (node->packet == NULL) {
1479 if (parser == NULL) {
1480 g_warning ("MmsSource::Pop (): No parser to parse the packet.\n");
1481 goto cleanup;
1483 node->packet = new ASFPacket (parser, node->source);
1484 if (!MEDIA_SUCCEEDED (node->packet->Read ())) {
1485 LOG_PIPELINE_ASF ("MmsSource::Pop (): Error while parsing packet, getting a new packet\n");
1486 delete node;
1487 goto trynext;
1491 result = node->packet;
1492 result->ref ();
1494 cleanup:
1495 delete node;
1497 if (parser)
1498 parser->unref ();
1500 LOG_PIPELINE_ASF ("MmsSource::Pop (): popped 1 packet, there are %i packets left, of a total of %" G_GINT64_FORMAT " packets written\n", queue.Length (), write_count);
1502 return result;
1505 bool
1506 MmsPlaylistEntry::IsFinished ()
1508 bool result;
1509 return parent->IsFinished (); // REMOVE
1510 MmsSource *src = GetParentReffed ();
1512 g_return_val_if_fail (src != NULL, true);
1514 result = src->IsFinished ();
1515 src->unref ();
1517 return result;
1520 void
1521 MmsPlaylistEntry::WritePacket (void *buf, gint32 n)
1523 MemorySource *src;
1524 ASFPacket *packet;
1525 ASFParser *asf_parser;
1526 Media *media;
1527 bool added = false;
1529 LOG_PIPELINE_ASF ("MmsPlaylistEntry::WritePacket (%p, %i), write_count: %" G_GINT64_FORMAT "\n", buf, n, write_count + 1);
1530 VERIFY_MAIN_THREAD;
1532 media = GetMediaReffed ();
1534 g_return_if_fail (media != NULL);
1536 write_count++;
1538 asf_parser = GetParserReffed ();
1540 if (asf_parser != NULL) {
1541 src = new MemorySource (media, buf, n, 0, false);
1542 packet = new ASFPacket (asf_parser, src);
1543 if (!MEDIA_SUCCEEDED (packet->Read ())) {
1544 LOG_PIPELINE_ASF ("MmsPlaylistEntry::WritePacket (%p, %i): Error while parsing packet, dropping packet.\n", buf, n);
1545 } else {
1546 queue.Push (new QueueNode (packet));
1547 added = true;
1549 packet->unref ();
1550 src->unref ();
1551 } else {
1552 src = new MemorySource (media, g_memdup (buf, n), n, 0);
1553 queue.Push (new QueueNode (src));
1554 src->unref ();
1555 added = true;
1558 if (added) {
1559 IMediaDemuxer *demuxer = GetDemuxerReffed ();
1560 if (demuxer) {
1561 demuxer->FillBuffers ();
1562 demuxer->unref ();
1565 if (asf_parser)
1566 asf_parser->unref ();
1568 if (media)
1569 media->unref ();
1573 * MmsPlaylistEntry::QueueNode
1576 MmsPlaylistEntry::QueueNode::QueueNode (MemorySource *source)
1578 if (source)
1579 source->ref ();
1580 this->source = source;
1581 packet = NULL;
1584 MmsPlaylistEntry::QueueNode::QueueNode (ASFPacket *packet)
1586 if (packet)
1587 packet->ref ();
1588 this->packet = packet;
1589 source = NULL;
1592 MmsPlaylistEntry::QueueNode::~QueueNode ()
1594 if (packet)
1595 packet->unref ();
1596 if (source)
1597 source->unref ();
1601 * MmsDemuxer
1604 MmsDemuxer::MmsDemuxer (Media *media, MmsSource *source)
1605 : IMediaDemuxer (Type::MMSDEMUXER, media, source)
1607 playlist = NULL;
1608 mms_source = source;
1609 if (mms_source)
1610 mms_source->ref ();
1613 void
1614 MmsDemuxer::GetFrameAsyncInternal (IMediaStream *stream)
1616 printf ("MmsDemuxer::GetFrameAsyncInternal (%p): This method should never be called.\n", stream);
1619 void
1620 MmsDemuxer::OpenDemuxerAsyncInternal ()
1622 PlaylistRoot *root;
1623 Media *media;
1625 LOG_MMS ("MmsDemuxer::OpenDemuxerAsyncInternal ().\n");
1627 media = GetMediaReffed ();
1628 root = media ? media->GetPlaylistRoot () : NULL;
1630 g_return_if_fail (playlist == NULL);
1631 g_return_if_fail (media != NULL);
1632 g_return_if_fail (root != NULL);
1634 playlist = new Playlist (root, mms_source);
1635 ReportOpenDemuxerCompleted ();
1636 media->unref ();
1639 MediaResult
1640 MmsDemuxer::SeekInternal (guint64 pts)
1642 g_warning ("MmsDemuxer::SeekInternal (%" G_GINT64_FORMAT "): You hit a bug in moonlight, please attach gdb, get a stack trace and file bug.", pts);
1643 print_stack_trace ();
1645 return MEDIA_FAIL;
1648 void
1649 MmsDemuxer::SeekAsyncInternal (guint64 seekToTime)
1651 printf ("MmsDemuxer::SeekAsyncInternal (%" G_GUINT64_FORMAT "): Not implemented.\n", seekToTime);
1654 void
1655 MmsDemuxer::SwitchMediaStreamAsyncInternal (IMediaStream *stream)
1657 printf ("MmsDemuxer::SwitchMediaStreamAsyncInternal (%p): Not implemented.\n", stream);
1660 void
1661 MmsDemuxer::Dispose ()
1663 Playlist *pl;
1664 MmsSource *src;
1666 mutex.Lock ();
1667 pl = this->playlist;
1668 this->playlist = NULL;
1669 src = this->mms_source;
1670 this->mms_source = NULL;
1671 mutex.Unlock ();
1673 if (pl)
1674 pl->unref ();
1676 if (src)
1677 src->unref ();
1679 IMediaDemuxer::Dispose ();