{"record":{"id":"db462edd6ef24c11","repo":"subhra74/xdm","slug":"missing-mpd-start-tag","errorCode":null,"errorMessage":"Missing MPD start tag: ","messagePattern":"Missing MPD start tag: ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/XDM/XDM.Core/MediaParser/Dash/MpdParser.cs","lineNumber":33,"sourceCode":"    public static class MpdParser\r\n    {\r\n        private static string trickModeUri = \"http://dashif.org/guidelines/trickmode\";\r\n        private static readonly Regex TemplatePattern =\r\n            new Regex(@\"\\$(RepresentationID|Time|Time%0(?<time_digits>[\\d]+)(?<time_dx>[dx])?|Number|Number%0(?<num_digits>[\\d]+)(?<num_dx>[dx])?|Bandwidth)\\$\");\r\n        //@\"\\$(RepresentationID|Time|Time(%0([\\d]+)([dx])?)?|Number|Number(%0([\\d]+)([dx])?)?|Bandwidth)\\$\");\r\n        public static IList<IList<KeyValuePair<Representation?, Representation?>>> Parse(\r\n            string[] manifestLines,\r\n            string playlistUrl)\r\n        {\r\n            using var memstream = new MemoryStream(Encoding.UTF8.GetBytes(string.Join(\"\\n\", manifestLines)));\r\n            var xmldoc = new XmlDocument();\r\n            xmldoc.Load(new XmlTextReader(memstream)\r\n            {\r\n                Namespaces = false\r\n            });\r\n\r\n            if (xmldoc.DocumentElement?.Name != \"MPD\")\r\n                throw new Exception(\"Missing MPD start tag: \" + xmldoc.DocumentElement.Name);\r\n            if (xmldoc.DocumentElement.Attributes[\"type\"]?.Value == \"dynamic\")\r\n                throw new Exception(\"Manifest type dynamic is not supported\");\r\n            if (xmldoc.DocumentElement.SelectSingleNode(\"descendant::ContentProtection\") != null)\r\n                throw new Exception(\"Encrypted manifest\");\r\n\r\n            var mediaList = new List<IList<KeyValuePair<Representation?, Representation?>>>();\r\n\r\n            var mediaPresentationDuration =\r\n                DashUtil.ParseXsDuration(xmldoc.DocumentElement.Attributes[\"mediaPresentationDuration\"]?.Value ?? \"0\");\r\n            var baseUrl = new Uri(playlistUrl);\r\n            var baseUrlNodeRoot = xmldoc.DocumentElement.SelectSingleNode(\"child::BaseURL\");\r\n            if (baseUrlNodeRoot != null)\r\n            {\r\n                baseUrl = UrlResolver.Resolve(baseUrl, baseUrlNodeRoot.InnerText);\r\n            }\r\n\r\n            var periods = xmldoc.DocumentElement.SelectNodes(\"child::Period\");\r\n            if (periods == null || periods.Count < 1) throw new Exception(\"No period found!\");\r","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/subhra74/xdm/blob/1ca5a25aae007826c859c81bea494e7c102e1242/app/XDM/XDM.Core/MediaParser/Dash/MpdParser.cs#L15-L51","documentation":"MpdParser.Parse loads the downloaded DASH manifest into an XmlDocument with namespaces disabled and requires the root element to be exactly \"MPD\". If the document's root element is missing or named anything else, it throws to indicate the downloaded content is not a DASH MPD manifest. This is a guard against feeding HTML error pages, HLS playlists, or other non-MPD content into the DASH parser.","triggerScenarios":"Calling Parse (via the DASH media-parser entry point) with a playlistUrl whose response body does not have a top-level <MPD> element — e.g. the URL returned an HTML login/error page, a redirect to a block page, an HLS m3u8 playlist, or an empty/truncated response.","commonSituations":"Server returns 200 with an HTML captcha or geo-block page instead of the manifest; user pasted an HLS (.m3u8) URL into a DASH-only flow; CDN misconfiguration serves an error document with HTTP 200; the manifest URL expired and the server responds with an error page.","solutions":["Check the actual HTTP response body for the playlist URL (curl it) and confirm it starts with <MPD.","Verify the URL is a DASH manifest (.mpd) and not an HLS playlist or a webpage; use the correct parser for m3u8 content.","Ensure cookies/headers/Referer required by the site are passed so the server returns the real manifest instead of a block/error page.","Handle the null/other root element before parsing by fetching and sniffing the content type yourself."],"exampleFix":"// before\nvar media = MpdParser.Parse(manifestUrl, null);\n\n// after\nvar body = new HttpClient().GetStringAsync(manifestUrl).Result;\nif (!body.TrimStart().StartsWith(\"<MPD\"))\n    throw new Exception(\"URL does not point to a DASH MPD manifest\");\nvar media = MpdParser.Parse(manifestUrl, null);","handlingStrategy":"validation","validationCode":"var body = await http.GetStringAsync(manifestUrl);\nif (!body.TrimStart().StartsWith(\"<MPD\", StringComparison.OrdinalIgnoreCase))\n    throw new Exception(\"Response is not a DASH MPD manifest\");","typeGuard":null,"tryCatchPattern":"try { var media = MpdParser.Parse(url, null); }\ncatch (Exception ex) when (ex.Message.StartsWith(\"Missing MPD start tag\"))\n{ Log.Warn(\"Not a DASH manifest: {0}\", url); }","preventionTips":["Sniff the response body/content-type before invoking the DASH parser","Route m3u8 URLs to an HLS parser instead","Pass required cookies/headers so servers don't return block pages","Check HTTP status and final redirect URL before parsing"],"tags":["dash","xml","manifest-parsing","media-parser"],"backgroundTag":"unexpected-response-shape","analyzedSha":"1ca5a25aae007826c859c81bea494e7c102e1242","analyzedAt":"2026-09-13T20:37:40.968Z","contentChangedAt":"2026-09-13T20:37:40.968Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}