/***************************************************** * * Copyright 2009 Adobe Systems Incorporated. All Rights Reserved. * ***************************************************** * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * * The Initial Developer of the Original Code is Adobe Systems Incorporated. * Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems * Incorporated. All Rights Reserved. * *****************************************************/ using System; using System.Collections.Generic; using System.Xml; namespace hdsdump.f4m { public class Manifest { /// <summary> /// The id element represents a unique identifier for the media. It is optional. /// </summary> public string id; /// <summary> /// The label element represents a user-friendly description for the media. It is optional. /// </summary> public string label; /// <summary> /// The lang element represents a language code identifier for the media. It is optional. /// </summary> public string lang; /// <summary> /// The <baseURL> element contains the base URL for all relative (HTTP-based) URLs /// in the manifest. It is optional. When specified, its value is prepended to all /// relative URLs (i.e. those URLs that don't begin with "http://" or "https://" /// within the manifest file. (Such URLs may include <media> URLs, <bootstrapInfo> /// URLs, and <drmMetadata> URLs.) /// </summary> public string baseURL; /// <summary> /// Indicate whether the media URL includes FMS application instance. This is only applicable to RTMP URLs. /// </summary> public bool urlIncludesFMSApplicationInstance = false; /// <summary> /// The <duration> element represents the duration of the media, in seconds. /// It is assumed that all representations of the media have the same duration, /// hence its placement under the document root. It is optional. /// </summary> public float duration; /// <summary> /// The <mimeType> element represents the MIME type of the media file. It is assumed /// that all representations of the media have the same MIME type, hence its /// placement under the document root. It is optional. /// </summary> public string mimeType; /// <summary> /// The <streamType> element is a string representing the way in which the media is streamed. /// Valid values include "live", "recorded", and "liveOrRecorded". It is assumed that all representations /// of the media have the same stream type, hence its placement under the document root. /// It is optional. /// </summary> public string streamType; /// <summary> /// Indicates the means by which content is delivered to the player. Valid values include /// "streaming" and "progressive". It is optional. If unspecified, then the delivery /// type is inferred from the media protocol. For media with an RTMP protocol, /// the default deliveryType is "streaming". For media with an HTTP protocol, the default /// deliveryType is also "streaming". In the latter case, the <bootstrapInfo> field must be /// present. /// </summary> public string deliveryType; /// <summary> /// Represents the date/time at which the media was first (or will first be) made available. /// It is assumed that all representations of the media have the same start time, hence its /// placement under the document root. The start time must conform to the "date-time" production /// in RFC3339. It is optional. /// </summary> public DateTime startTime; /// <summary> /// The set of different bootstrap information objects associated with this manifest. /// </summary> public List<BootstrapInfo> bootstrapInfos = new List<BootstrapInfo>(); /// <summary> /// The set of different |AddionalHeader objects associated with this manifest. /// </summary> public List<DRMAdditionalHeader> drmAdditionalHeaders = new List<DRMAdditionalHeader>(); /// <summary> /// The set of different bitrate streams associated with this media. /// </summary> public List<Media> media = new List<Media>(); /// <summary> /// The set of alternative streams associated with this media. /// </summary> public List<Media> alternativeMedia = new List<Media>(); /// <summary> /// The dvrInfo element. It is needed to play DVR media. /// </summary> public DVRInfo dvrInfo = null; public BestEffortFetchInfo bestEffortFetchInfo = null; public List<CueInfo> cueInfos = new List<CueInfo>(); // CONSTRUCTOR public Manifest(XmlNodeEx nodeManifest, string rootURL = "", string idPrefix = "", int nestedBitrate = 0) { Parse(nodeManifest, rootURL, idPrefix, nestedBitrate); } #region Parser public void Parse(XmlNodeEx nodeManifest, string rootURL = "", string idPrefix = "", int nestedBitrate = 0) { id = nodeManifest.GetText("id"); label = nodeManifest.GetText("label"); lang = nodeManifest.GetText("lang"); duration = nodeManifest.GetFloat("duration"); startTime = nodeManifest.GetDateTime("startTime"); mimeType = nodeManifest.GetText("mimeType"); streamType = nodeManifest.GetText("streamType"); deliveryType = nodeManifest.GetText("deliveryType"); baseURL = nodeManifest.GetText("baseURL"); urlIncludesFMSApplicationInstance = nodeManifest.GetAttributeBoolean("urlIncludesFMSApplicationInstance"); if (string.IsNullOrEmpty(baseURL)) { baseURL = rootURL; } baseURL = URL.normalizePathForURL(baseURL, false); XmlNodeEx nodeDVR = nodeManifest.GetChildNode("dvrInfo") as XmlNodeEx; dvrInfo = (nodeDVR != null) ? new DVRInfo(nodeDVR) : null; // cueInfo cueInfos.Clear(); List<XmlNodeEx> cueInfoNodes = nodeManifest.GetChildNodesByName("cueInfo"); foreach (XmlNodeEx nodeCueInfo in cueInfoNodes) { string cueInfoId = nodeCueInfo.GetAttributeStr("id", F4MUtils.GLOBAL_ELEMENT_ID); CueInfo cueInfo = new CueInfo(cueInfoId); foreach (XmlNodeEx node in nodeCueInfo.GetChildNodesByName("cue")) { cueInfo.Cues.Add(new Cue(node, baseURL, idPrefix)); } cueInfos.Add(cueInfo); } media .Clear(); alternativeMedia .Clear(); drmAdditionalHeaders.Clear(); bootstrapInfos .Clear(); foreach (XmlNode childNode in nodeManifest.ChildNodes) { XmlNodeEx childNodeEx = childNode as XmlNodeEx; if (childNodeEx == null) continue; switch (childNodeEx.Name) { case "media": Media mediaItem = new Media(childNodeEx, baseURL, idPrefix, nestedBitrate); if (mediaItem.bitrate == 0) { mediaItem.bitrate = nodeManifest.GetAttributeInt("bitrate"); } if (mediaItem.alternate) alternativeMedia.Add(mediaItem); else media.Add(mediaItem); break; case "drmAdditionalHeader": drmAdditionalHeaders.Add(new DRMAdditionalHeader(childNodeEx, baseURL, idPrefix)); break; case "bootstrapInfo": bootstrapInfos.Add(new BootstrapInfo(childNodeEx, baseURL, idPrefix)); break; } } XmlNodeEx nodeBEF = nodeManifest.GetChildNode("bestEffortFetchInfo") as XmlNodeEx; bestEffortFetchInfo = (nodeBEF != null) ? new BestEffortFetchInfo(nodeBEF) : null; // Adaptive sets search List<XmlNodeEx> adaptiveSet = nodeManifest.GetChildNodesByName("adaptiveSet"); foreach(XmlNodeEx nodeSet in adaptiveSet) { string alternate = nodeSet.GetAttributeStr("alternate"); string audioCodec = nodeSet.GetAttributeStr("audioCodec"); string label = nodeSet.GetAttributeStr("label"); string lang = nodeSet.GetAttributeStr("lang"); string type = nodeSet.GetAttributeStr("type"); List<XmlNodeEx> mediaInSet = nodeSet.GetChildNodesByName("media"); foreach (XmlNodeEx nodeMedia in mediaInSet) { Media mediaItem = new Media(nodeMedia, baseURL, idPrefix, nestedBitrate); if (mediaItem.bitrate == 0) mediaItem.bitrate = nodeManifest.GetAttributeInt("bitrate"); if (!string.IsNullOrEmpty(alternate)) mediaItem.alternate = true; if (!string.IsNullOrEmpty(audioCodec) && string.IsNullOrEmpty(mediaItem.audioCodec)) mediaItem.audioCodec = audioCodec; if (!string.IsNullOrEmpty(label) && string.IsNullOrEmpty(mediaItem.label)) mediaItem.label = label; if (!string.IsNullOrEmpty(lang) && string.IsNullOrEmpty(mediaItem.lang)) mediaItem.lang = lang; if (!string.IsNullOrEmpty(type) && string.IsNullOrEmpty(mediaItem.type)) mediaItem.type = type; if (mediaItem.alternate) alternativeMedia.Add(mediaItem); else media.Add(mediaItem); } } GenerateRTMPBaseURL(); } /// <summary> /// Ensures that an RTMP based Manifest has the same server for all /// streaming items, and extracts the base URL from the streaming items /// if not specified. /// </summary> private void GenerateRTMPBaseURL() { if (string.IsNullOrEmpty(baseURL)) { foreach (var mediaItem in media) { if (NetStreamUtils.isRTMPStream(mediaItem.url)) { baseURL = mediaItem.url; break; } } } } #endregion Parser } }