Skip to content

canPlay.dash / canPlay.hls return false when the manifest URL carries a #t= media fragment #2046

Description

@danihorvath

Summary

DASH_EXTENSIONS and HLS_EXTENSIONS only allow .mpd/.m3u8 to be followed by end-of-string or ?, so a manifest URL carrying a media fragment (#t=) fails detection. VIDEO_EXTENSIONS explicitly allows #t=, so behaviour is inconsistent between progressive files and manifests:

// src/patterns.ts
export const VIDEO_EXTENSIONS = /\.(mp4|og[gv]|webm|mov|m4v)(#t=[,\d+]+)?($|\?)/i; // handles #t=
export const HLS_EXTENSIONS = /\.(m3u8)($|\?)/i;   // does not
export const DASH_EXTENSIONS = /\.(mpd)($|\?)/i;   // does not

This matters because #t= on a manifest is a legitimate, supported way to start a stream at an offset:

  • dash.js parses the URL fragment via URIFragmentModel and uses t to compute the start time, so attachSource('movie.mpd#t=258') starts at 4:18.
  • Native HLS in Safari: hls-video-element assigns the URL straight to nativeEl.src, where the browser honours the media fragment.

Impact

Because index.ts passes the html entry as playerFallback, a non-matching URL does not render nothing — it silently falls through to HtmlPlayer and produces a plain <video> pointed at the manifest. In any browser without native DASH support that is an unplayable source, so the symptom looks like a broken stream or a broken CDN rather than a URL-detection problem.

Reproduction

<ReactPlayer src="https://example.com/movie.mpd" />      // -> <dash-video src="...">   OK
<ReactPlayer src="https://example.com/movie.mpd#t=258" /> // -> <video src="...">       dash.js never loads

Rendered output, confirmed in jsdom on 3.3.3 and 3.4.0:

PLAIN   .mpd        ->  <dash-video src="https://example.com/movie.mpd">
WITH    .mpd#t=258  ->  <video      src="https://example.com/movie.mpd#t=258">

Related: #t= with a decimal never matches either

(#t=[,\d+]+)? has no . in its character class, so fractional times fail even for progressive files. (The + inside the class is a literal, which also looks unintended.)

src matched player
a.mpd dash
a.mpd#t=258 none
a.m3u8 hls
a.m3u8#t=258 none
a.mp4#t=258 html
a.mp4#t=10,20 html
a.mp4#t=10.5 none
a.mp3#t=5 none

Fractional start positions are common when resuming playback from a stored position, and AUDIO_EXTENSIONS has no fragment handling at all.

Suggested fix

Treat # like ? — a fragment simply terminates the extension — instead of special-casing one fragment shape:

export const AUDIO_EXTENSIONS = /\.(m4a|m4b|mp4a|mpga|mp2|mp2a|mp3|m2a|m3a|wav|weba|aac|oga|spx)($|[?#])/i;
export const VIDEO_EXTENSIONS = /\.(mp4|og[gv]|webm|mov|m4v)($|[?#])/i;
export const HLS_EXTENSIONS = /\.(m3u8)($|[?#])/i;
export const DASH_EXTENSIONS = /\.(mpd)($|[?#])/i;

This collapses the special-cased #t= group into a general terminator and fixes decimals, commas, posix: times and any other fragment form in one go.

Workaround

Re-registering the built-in entries with a fragment-tolerant canPlay, since custom players are consulted first:

import ReactPlayer from 'react-player'
import Players from 'react-player/players'

for (const [key, pattern] of Object.entries({ hls: /\.m3u8($|[?#])/i, dash: /\.mpd($|[?#])/i })) {
  const player = Players.find((entry) => entry.key === key)
  if (player) ReactPlayer.addCustomPlayer({ ...player, canPlay: (src) => pattern.test(src) })
}

Environment

  • react-player 3.3.3 and 3.4.0 — dist/patterns.js is byte-identical between them, and src/patterns.ts on master is unchanged too
  • dash.js 5.2.0, dash-video-element 0.2.0, hls-video-element 1.5.11

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions