Media.CanPlay(path, profile) reads a file’s container and codec headers and says whether a browser plays it, as a downloaded file or through Media Source Extensions. When it doesn’t, the verdict lists each blocker with a code, the track it concerns and the fix: a remux, an audio re-encode, or nothing this library can do (the video codec). The inputs are MDN’s CC0 flower clip as MP4 (H.264/AAC) and WebM (VP8/Vorbis), the Sintel trailer (CC BY 3.0, © copyright Blender Foundation | durian.blender.org) as MP4 and copied into AVI with FFmpeg, and test5.mkv from the Matroska project’s test files (cut from Elephants Dream, CC BY: © copyright 2006, Blender Foundation / Netherlands Media Art Institute / www.elephantsdream.org).
dotnet new console -o CanPlay && cd CanPlay dotnet add package RecursiveAI.OneMedia --version 0.3.0-preview.1 m=https://interactive-examples.mdn.mozilla.net/media/cc0-videos curl -L -o flower.mp4 $m/flower.mp4 curl -L -o flower.webm $m/flower.webm curl -L -o sintel.mp4 https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4 curl -L -o test5.mkv https://raw.githubusercontent.com/ietf-wg-cellar/matroska-test-files/master/test_files/test5.mkv ffmpeg -v error -i sintel.mp4 -c copy sintel.avi
Put this in Program.cs and run dotnet run:
using RecursiveAI.OneMedia; using RecursiveAI.OneMedia.Playback; string[] files = ["flower.mp4", "flower.webm", "sintel.mp4", "test5.mkv", "sintel.avi"]; BrowserProfile[] profiles = [BrowserProfiles.Chrome, BrowserProfiles.Chromium, BrowserProfiles.Firefox, BrowserProfiles.Safari, BrowserProfiles.GenericMp4]; foreach (string path in files) { Console.WriteLine(path); foreach (BrowserProfile profile in profiles) { PlaybackVerdict verdict = Media.CanPlay(path, profile); string answer = verdict.CanPlay ? "plays" : "no: " + string.Join(", ", verdict.Blockers.Select(b => $"{b.Code} (track {b.TrackIndex}, {b.Remedy})")); string warnings = string.Concat(verdict.Progressive.Warnings.Select(w => $" [warning {w.Code}]")); Console.WriteLine($" {profile.Id,-12} {answer}{warnings}"); } }
flower.mp4 chrome plays chromium no: codec.unsupported (track 0, VideoBlocker), codec.unsupported (track 1, TranscodeAudio) firefox plays safari plays generic-mp4 plays flower.webm chrome plays chromium plays firefox plays safari plays generic-mp4 no: container.unsupported (track -1, Remux), codec.unsupported (track 0, VideoBlocker), codec.unsupported (track 1, TranscodeAudio) sintel.mp4 chrome plays [warning progressive.movie-at-end] chromium no: codec.unsupported (track 0, VideoBlocker), codec.unsupported (track 1, TranscodeAudio) [warning progressive.movie-at-end] firefox plays [warning progressive.movie-at-end] safari plays [warning progressive.movie-at-end] generic-mp4 plays [warning progressive.movie-at-end] test5.mkv chrome plays chromium no: codec.unsupported (track 0, VideoBlocker), codec.unsupported (track 1, TranscodeAudio), codec.unsupported (track 8, TranscodeAudio) firefox no: container.unsupported (track -1, Remux), codec.container (track 0, Remux), codec.container (track 1, Remux), codec.container (track 8, Remux) safari no: container.unsupported (track -1, Remux), codec.container (track 0, Remux), codec.container (track 1, Remux), codec.container (track 8, Remux) generic-mp4 no: container.unsupported (track -1, Remux), codec.container (track 0, Remux), codec.container (track 1, Remux), codec.container (track 8, Remux) sintel.avi chrome no: container.unsupported (track -1, Remux), codec.container (track 0, Remux), codec.container (track 1, Remux) chromium no: container.unsupported (track -1, Remux), codec.unsupported (track 0, VideoBlocker), codec.unsupported (track 1, TranscodeAudio) firefox no: container.unsupported (track -1, Remux), codec.container (track 0, Remux), codec.container (track 1, Remux) safari no: container.unsupported (track -1, Remux), codec.container (track 0, Remux), codec.container (track 1, Remux) generic-mp4 no: container.unsupported (track -1, Remux), codec.container (track 0, Remux), codec.container (track 1, Remux)
Track -1 is the container. Remux means the same packets play in another container; TranscodeAudio, that an audio track has to be re-encoded; VideoBlocker, that the video codec itself doesn’t play there, which OneMedia never re-encodes. verdict.CanBeMadePlayable is true when no blocker is a video blocker, and Make a video play in browsers applies the fixes. b.Message has a sentence for each blocker, such as “Chromium does not play h264.”
The Sintel MP4 plays everywhere but carries the warning progressive.movie-at-end: its index is at the end, so a browser has to fetch the end of the file before it starts. MP4 faststart fixes that. Warnings never block.
What a profile means
The profiles are static data, not a browser probe at run time. The project’s playability page defines them:
chrome: Chrome on Windows, macOS, Linux (proprietary codecs present). Evidence: verified: Chrome 156.0.8078.74, macOS arm64.chromium: Chromium built without proprietary codecs (also most Linux distribution builds, embedded browsers). Evidence: verified: Chromium 140.0.7339.16, Linux x64.firefox: Firefox where the OS or system libraries supply H.264, HEVC and AAC. Evidence: verified: Firefox 141.0, Linux x64, system FFmpeg libraries.safari: the WebKit engine on macOS. Evidence: verified: WebKit 26.0 (Playwright build), macOS arm64.webkit-linux: WebKit (WPE) with GStreamer and its FFmpeg plug-ins. Evidence: verified: WebKit 26.0 (Playwright WPE build), Linux x64.ios-safari: Safari and every iOS browser. Evidence: public documentation only.android-chrome: Chrome on Android. Evidence: public documentation only.generic-mp4: what all of the above accept: H.264 Baseline/Main/High level <= 4.2, 4-2-0 8-bitavc1, AAC-LC, MP3. Evidence: derived: the intersection.
The project checked the five verified profiles by playing 75 files (16 also through Media Source Extensions) in Chromium 140, Firefox 141 and WebKit 26: the verdict equals what was observed in all 455 cases. Its safari row was observed in Playwright’s macOS WebKit build, which shares Safari’s media stack but isn’t Safari.
Checked in headless Chrome
The chrome column was checked for this page in headless Chrome 149 on Linux (a build with H.264 and AAC). Each file was served over HTTP with range requests and loaded into a <video> element; canPlayType got the type and the codecs string OneMedia derives, the video was played for 2.5 s, and the audio level was measured through the Web Audio API:
file canPlayType readyState duration played to frames audio peak flower.mp4 probably 4 5.055 s 2.51 s 79 0 flower.webm probably 4 5.059 s 2.51 s 80 0 sintel.mp4 probably 4 52.208 s 2.50 s 64 0.088 test5.mkv probably 4 46.665 s 2.52 s 65 0.634 sintel.avi "" 0 — error 4 0 0
Chrome played the four files the chrome profile says it plays, and refused the AVI as the verdict says: error 4, MEDIA_ERR_SRC_NOT_SUPPORTED (“DEMUXER_ERROR_COULD_NOT_OPEN”), with canPlayType("video/x-msvideo") empty. The flower clip’s audio level is 0 because its audio track is silent (FFmpeg measures −91 dB throughout). The Chromium, Firefox and Safari columns were not run for this page; they rest on the project’s checks above.
Limits
- Headers only. A verdict reads the container header and codec configuration. It doesn’t scan samples, so a file with badly ordered blocks or a feature its header doesn’t announce can still fail; hardware decoders, DRM, codec levels enforced from the real frame rate and autoplay policy are outside it.
- Unverified profiles.
ios-safariandandroid-chromecome from MDN’s codec guides and Chrome’s and Apple’s public media notes, not from devices. Thechromeprofile was verified on Chrome 156; other versions can differ. - Rows no test file exercises (Vorbis in MP4 for Chromium, HEVC in MOV) are left out of a profile, which makes the verdict “blocked”.
- AVI and other remux candidates. A
Remuxremedy for an AVI holdingavc1H.264, as here, doesn’t hold in 0.3.0-preview.1: the remux drops that video track. See the limits of Make a video play in browsers.
To get the RFC 6381 codecs string for a <source type> attribute, see Get video duration, resolution and codec.