ASP.NET Core 9 MVCで動画再生がHTTP 416になる原因と解決策|UseStaticFilesでRange対応

ASP.NET Core 9 の MVC アプリでサーバー上の MP4 を <video> タグで再生しようとすると、HTTP 416(Requested Range Not Satisfiable)が返って再生できないことがあります。本記事では原因になりやすい静的ファイル設定を整理し、UseStaticFiles で確実に直す手順と切り分けポイントをまとめます。

目次

ASP.NET Core 9 MVCで動画再生がHTTP 416になるときの結論:UseStaticFiles() を入れる

HTML5 の <video> は、再生開始やシーク(再生位置の移動)のために Range ヘッダー付きの部分取得(バイトレンジ) を行います。サーバー側がこのレンジ要求に正しく応答できないと、ブラウザは再生を諦めたり、今回のように HTTP 416 を受け取って停止します。

.NET Core 9(ASP.NET Core 9)の MVC で「静的ファイルの配信」を構成する際、app.MapStaticAssets(); だけに頼ると動画のレンジ要求が期待どおりに処理されないケースがあります。対策としてはシンプルで、app.UseStaticFiles(); を有効化し、動画ファイルを wwwroot 配下から配信する ことで解消することが多いです。

症状主な原因最短の対策
<video> で読み込むと 416 になる静的ファイル配信が未設定/レンジ要求を処理できないUseStaticFiles() を追加し、wwwroot から配信
再生はできるがシークすると止まる206/Content-Range が返らず Range が成立していないレスポンスヘッダーを確認し、ミドルウェア順序も見直す
特定の環境やバージョンでだけ発生する.NET 9.x の既知不具合/テンプレート差分/プロキシ影響.NET と Visual Studio を更新し、環境差分を潰す

HTTP 416(Requested Range Not Satisfiable)の原因:HTML5 videoのRangeリクエスト

HTTP 416 は、ざっくり言うと「指定されたバイト範囲で返せるデータがない」という意味です。動画再生では次のような流れがよく起きます。

  • ブラウザが Range: bytes=0- のように、先頭からの部分取得を要求する
  • サーバーが 206 Partial Content と Content-Range を返す
  • 必要に応じてブラウザが別の範囲(例:bytes=1000000-)を要求し、シークやバッファリングを行う

このとき、サーバー側が Range を理解しない/ファイルサイズを正しく認識できない/ミドルウェアが途中でレスポンスを書き換える、などが起きると、レンジ処理が破綻して 416 になります。

レンジ要求が正常なときの「期待値」

観点期待される内容(例)意味
リクエストRange: bytes=0-先頭から(末尾まで)を部分取得したい
ステータス206 Partial Content部分データを返せている
レスポンスヘッダーAccept-Ranges: bytesこのリソースはバイトレンジに対応
レスポンスヘッダーContent-Range: bytes 0-999/1234567返した範囲と全体サイズ
レスポンスヘッダーContent-Type: video/mp4ブラウザが動画として扱える

MapStaticAssets() と UseStaticFiles() の違い:動画は後者が堅い

結論だけでなく、なぜ UseStaticFiles が効くのかを理解しておくと、今後の応用やトラブルシュートが一気にラクになります。

項目app.MapStaticAssets()app.UseStaticFiles()
仕組みエンドポイント(ルーティング)として静的アセットを公開する発想Static File Middleware がファイルを直接配信する定番方式
対象構成によってはビルド時に解決されるアセット中心になりやすいwwwroot 配下の実ファイルを URL にマップ
動画の Range 対応構成やミドルウェア順序次第で期待どおりにならないことがあるレンジ要求(部分取得)に対応しやすく、実績が多い
情報量比較的新しいため、事例が少ないことがある事例が豊富で、調べながら潰しやすい

「MapStaticAssets は全部ダメ」という話ではなく、動画のようにレンジ対応が必須で、ファイルサイズも大きくなりやすいコンテンツは UseStaticFiles で配信するのが安全、という整理です。静的アセット周りを併用したい場合でも、まず動画については UseStaticFiles に寄せるのが現実的です。

最小構成の Program.cs 例(重要:ミドルウェアの順番)

動画が 416 になるとき、意外と多いのが「ミドルウェアの順番」のミスです。静的ファイルは UseRouting() より前で処理されるのが基本です。

var builder = WebApplication.CreateBuilder(args);

// MVC を利用する場合のサービス登録
builder.Services.AddControllersWithViews();

var app = builder.Build();

// ここで静的ファイル(wwwroot 配下)を有効化:動画もここから配信される
app.UseStaticFiles();

// ルーティング・認証など
app.UseRouting();
app.UseAuthorization();

// ルート設定
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

プロジェクトによっては、次のようなミドルウェアも追加しているはずです。重要なのは「静的ファイルが先」である点です。

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

// ここが先(Static File Middleware)
app.UseStaticFiles();

app.UseRouting();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

動画ファイルの配置:wwwroot/videos と URL の対応を確認する

UseStaticFiles は既定で wwwroot を公開します。動画ファイルは次のような配置にするのが分かりやすく、トラブルが減ります。

ファイルの置き場所(物理パス)ブラウザからの URL最初に確認すること
wwwroot/videos/sample.mp4/videos/sample.mp4URL を直打ちして 200/206 が返るか
wwwroot/media/movie/intro.mp4/media/movie/intro.mp4大文字小文字・拡張子を含め完全一致しているか

ブラウザで /videos/sample.mp4 を直接開けない場合、<video> 側を疑う前に「静的ファイルとして配信できているか」を先に解消するのが近道です。

<video> タグの例(HTML)

type 属性を付けるとブラウザの判定が安定します。preload="metadata" は、最初に必要以上のダウンロードをしないための指定で、トラブルシュート時にも挙動が読みやすくなります。

&lt;video controls preload="metadata" width="640"&gt;
  &lt;source src="/videos/sample.mp4" type="video/mp4"&gt;
  お使いのブラウザは video タグに対応していません。
&lt;/video&gt;

動画を別フォルダに置きたい場合:追加の静的ファイルディレクトリをマップする

「容量が大きいので wwwroot とは別のフォルダに置きたい」「運用上の都合で動画だけ別ディレクトリにしたい」というケースもあります。その場合は、PhysicalFileProvider を使って 追加の公開ディレクトリ をマップできます。

using Microsoft.Extensions.FileProviders;

app.UseStaticFiles(); // 既定の wwwroot

// 例:アプリ直下の "Videos" フォルダを /videos として公開する
var videosPath = Path.Combine(app.Environment.ContentRootPath, "Videos");

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(videosPath),
    RequestPath = "/videos"
});

この方法は便利ですが、指定したフォルダが URL で誰でもアクセス可能 になります。認証が必要な動画には使わず、公開してよいファイルに限定するのが安全です(認証が必要なら後述の Controller 配信が向いています)。

開発者ツールで「Range が付いているか」を見る

HTTP 416 の切り分けは、ブラウザの開発者ツール(F12)で一気に進みます。

  1. ブラウザで対象ページを開き、F12 で開発者ツールを表示する
  2. 「ネットワーク」タブを開き、動画ファイル(.mp4)のリクエストを選ぶ
  3. リクエストヘッダーに Range が付与されているか確認する(例:Range: bytes=0-)
  4. レスポンスのステータスが 206 か、少なくとも 416 以外になっているか確認する

Range が付いているのに 416 が返る場合、サーバー側のレンジ処理が壊れている可能性が高いです。逆に、Range が付いていないのに 416 の場合は、プロキシやミドルウェアがヘッダーを変形している可能性も疑います。

コマンドで確認したい場合(curl)

ローカルや検証環境で再現するなら、curl で「レンジ要求に応答できるか」を確認できます。

# 先頭の 1 バイトだけ取りに行く(Range のテスト)
curl -I -H "Range: bytes=0-1" https://example.com/videos/sample.mp4

206 と Content-Range が返れば、少なくともレンジの基本動作は通っています。

.NET / Visual Studio の更新で直るケースもある(少なくとも .NET 9.0.2 以降を推奨)

静的ファイル配信やレンジ処理は OS・ホスティング環境・フレームワークの更新の影響を受けやすい領域です。特に .NET 9.x では、同様の症状が報告され「パッチ更新で解消した」というケースがあります。

  • Visual Studio を最新バージョンへ更新する
  • .NET SDK / Runtime を 9.0.2 以降 など、十分にパッチが当たった版へ更新する

「設定は合っているはずなのに挙動が変わらない」「別 PC では再現しない」といったときは、まず開発環境と実行環境の .NET バージョンを揃えるのが効果的です。

追加の確認ポイント:ここで詰まる人が多い

UseStaticFiles を入れても直らない場合、次の観点を上から潰すと原因に辿り着きやすいです。

チェック項目何が起きる?対処の方向性
ファイルパスと URL が一致しているか404 や別ファイルを参照して 416 になるURL 直打ちで 200/206 を確認。大文字小文字も含め完全一致
MIME タイプが video/mp4 になっているかブラウザが動画と認識せず挙動が不安定レスポンスヘッダーを確認。必要なら ContentTypeProvider を追加
UseStaticFiles() の位置が UseRouting() より前か静的ファイルに到達せず MVC で処理され、Range が壊れるミドルウェア順序を修正
レスポンス圧縮の対象に動画を入れていないか圧縮されたストリームで Range が成立せずエラーになることがあるvideo/mp4 を圧縮対象から外す(通常は不要)
リバースプロキシ / CDN が Range をブロックしていないかRange が落ちて 200 になったり、変形して 416 になるプロキシ設定(ヘッダー転送、キャッシュ、Range 許可)を確認
ファイルが配信中に更新されていないかサイズが変わり Range が不正になることがある配信中にファイルを更新しない/別名で差し替える

MIME タイプが怪しいときの例(.mp4 を明示)

通常は .mp4 は既定で video/mp4 にマップされますが、環境差やカスタム設定で崩れている場合は明示できます。

using Microsoft.AspNetCore.StaticFiles;

var contentTypeProvider = new FileExtensionContentTypeProvider();
contentTypeProvider.Mappings[".mp4"] = "video/mp4";

app.UseStaticFiles(new StaticFileOptions
{
ContentTypeProvider = contentTypeProvider
}); 

wwwroot に置けない場合:Controller で返すなら Range を有効化する

認証が必要な動画や、公開ディレクトリに置けないファイルを配信したい場合は、MVC のアクションからファイルを返すことがあります。このときは Range 処理を有効化 しないと、ブラウザのレンジ要求にうまく応答できず、416 やシーク不可につながります。

PhysicalFile で Range を有効化する例

using Microsoft.AspNetCore.Mvc;

public class VideosController : Controller
{
[HttpGet("/secure-videos/{fileName}")]
public IActionResult Get(string fileName)
{
var filePath = Path.Combine(
Directory.GetCurrentDirectory(),
"SecureVideos",
fileName);


    if (!System.IO.File.Exists(filePath))
    {
        return NotFound();
    }

    // enableRangeProcessing: true がポイント
    return PhysicalFile(
        filePath,
        "video/mp4",
        enableRangeProcessing: true);
}


} 

この方式なら、認可(ログイン必須、購入者のみ視聴など)を Controller で制御しながら、ブラウザが求める Range 応答(206/Content-Range)にも対応しやすくなります。

よくある質問

MapStaticAssets() を残したままでも大丈夫?

プロジェクトの構成によっては併用できます。重要なのは「動画の配信経路が Range 要求に耐えるか」です。迷ったら、まず動画は UseStaticFiles(または Controller の Range 有効化)で安定させ、その上で他のアセット配信を整理すると失敗しにくいです。

416 ではなく 200 が返っている。これでも再生できる?

再生自体は 200 でも始まることがありますが、シークや途中再生が不安定になりがちです。<video> が Range を使っているのに 200 しか返らない場合は、どこかで Range が落ちている可能性があります。ネットワークタブで Range の有無を確認してください。

動画のファイルサイズが大きいほど起きやすい?

大きいファイルほどレンジ要求・バッファリング・キャッシュの影響を受けやすいので、設定不備が表面化しやすい傾向があります。まずは小さな MP4 で動作を安定させ、同じ手順で大きい動画へ広げると切り分けがスムーズです。

まとめ:HTTP 416 は「Range に正しく応答できていない」サイン

  • <video> は Range ヘッダー付きで部分取得するため、サーバー側の静的ファイル配信設定が重要
  • app.MapStaticAssets(); だけで詰まる場合は、まず app.UseStaticFiles(); を入れる
  • 動画は wwwroot 配下(または追加公開ディレクトリ)に配置し、URL 直打ちで 200/206 を確認する
  • 開発者ツールで Range とレスポンス(206/Content-Range)を確認すると原因が見えやすい
  • wwwroot に置けない場合は Controller 返却時に enableRangeProcessing: true を使う

最終的に、UseStaticFiles に変更したことで動画配信が正常化し、HTTP 416 が解消した、という流れは非常に典型です。まずは静的ファイルの配信経路を一本化し、Range が通る状態を作ってから、必要に応じて運用(認証、プロキシ、キャッシュ)を整えていくのがおすすめです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次