---
title: "Check if a browser can play a video in C#"
url: https://recursiveai.org/onemedia/cookbook/check-browser-can-play-video/
description: "Check whether a browser can play a video file in C#: OneMedia’s CanPlay says if Chrome, Chromium, Firefox, Safari or a generic MP4 profile plays an MP4, WebM, MKV or AVI file, and names what blocks it (container, codec, profile) and what would fix it. Compared with headless Chrome."
version: "0.3.0-preview.2"
---

# Check if a browser can play a video in C#

`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).

```shell
dotnet new console -o CanPlay && cd CanPlay
dotnet add package RecursiveAI.OneMedia --version 0.3.0-preview.2
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`:

```csharp
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}");
    }
}
```

```text
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](https://recursiveai.org/onemedia/cookbook/make-video-playable-in-browser/) applies the fixes. For `sintel.avi` and `chrome` that is a remux into MP4: `onemedia playable sintel.avi sintel-avi.mp4 --profile chrome` reports `Made (Mp4)`, and ffprobe decodes all 1,253 video frames of the result. `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](https://recursiveai.org/onemedia/cookbook/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](https://recursiveai.org/onemedia/docs/playability/) 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-bit `avc1`, 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:

```text
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-safari` and `android-chrome` come from MDN’s codec guides and Chrome’s and Apple’s public media notes, not from devices. The `chrome` profile 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”.

To get the RFC 6381 codecs string for a `<source type>` attribute, see [Get video duration, resolution and codec](https://recursiveai.org/onemedia/cookbook/get-video-duration-resolution-codec/).
