Stutter on some files on web client (and some other clients) on some files

Server Version#: 1.42.2.10156
Player Version#: 4.147.1

Webclient stutters (and some users have reported on non-web) that SOME files will stutter. this is clearly a client problem (and not a server problem) or a plex server problem telling the client how to decode or what the file is. Once I turn up transcoding (aka reduce the resolution to force a transcode the stutter disappears) but I would like it if users didn’t have to transcode.

Attached are the mediainfo of the two files they are nearly identical from what I can see. (Obviously 2 different videos but same codec, same resolution, same encoder and ripping group) some something is very weird here.

plexWebClientDebugLogs.txt (3.7 KB)

MediaInfo-Stutter-hp.mediainfo.txt (5.4 KB)

MediaInfo-HA-no-stutter-265.mediainfo.txt (5.2 KB)

Now a non-stuttering file also 265

https://youtu.be/S73gGz100RI

any hints would be appreciated.

I’ve had similar issues with some files - I think it ended up being something with how they were encoded. I was able to remux them (with no loss) to fix the issue but can’t recall how I did it now lol.

The weird thing is on the codec section of the video file they are almost exactly the same….which deepens the mystery.

I just found a random movie in my library that acts the same way. Let me try a few quick things to see how to resolve it.

And FWIW, it only stutters when playing in a browser. Tried with both Edge and Chrome.

@Lazarus_Long I get the work around….sounds similar to forcing a transcode…when I force a transcode it smooths out. The problem (I think) is on the client when it is direct playing. There is SOMETHING in the file or codec that is just not mixing well. I think this is some sort of communication problem between plex and the client.

@ChuckPa Long time and happy New Year! Do you know who on the plex team might be the right person to look at this?

The fix (and I just did a quick test), was to change the container. My original container was MP4. I just opened the file in MKVToolNix and didn’t change any settings - letting it remux into an MKV container.

After that Chrome plays without stutter. Only take maybe a minute on my older system to do it.

Hey @Lazarus_Long I am not sure I would call that a fix (although I do appreciate the effort) this is a work around. I think you would agree that we shouldn’t have to change the container to avoid stuttering?

Shouldn’t have to, but some quick Googling seemed to me that it might be more of an issue with Chrome and how it plays HEVC files. Might not be anything Plex can actually do to fix it.

@Lazarus_Long yes BUT….look at the two mediainfo files for the two videos, they are BOTH HEVC. So the theory of this happening b/c of HEVC I don’t think is the right conclusion. LMK if you have a different viewpoint:

Video1:

Format : HEVC
Format/Info : High Efficiency Video Coding
Format profile : Main 10@L4@Main
Codec ID : hev1
Codec ID/Info : High Efficiency Video Coding

Video2:
Format : HEVC
Format/Info : High Efficiency Video Coding
Format profile : Main 10@L4@Main
Codec ID : hev1
Codec ID/Info : High Efficiency Video Coding

Unfortunately I don’t know enough about containers and how Chrome handles them, but I assume even though the content inside is the same, Chrome still needs to open the container in some fashion and maybe that’s the difference?

I know I had the same issues with Emby and Jellyfin when I’ve tested both of them recently, so pretty sure it’s not a Plex issue but more an issue with how Chrome is handling MP4 containers.

One blog suggested disabling hardware acceleration in Chrome - just tried that and that ended up making everything coming from Plex transcode (although without stutter lol)

So I found this post on the Jellyfin Github issues list regarding the same issue. Appears to be related to the first frame in the MP4 file being incorrect type (should be “I”).

Someone wrote a bash script that can identify all files that have that issue - and sure enough, every one it indicated in my library has the stutter. Also, almost all of them came from a specific release group (won’t post that here). In my case, the first frame is “B” instead of “I”.

I checked the file I remuxed into MKV and that first frame is fixed properly.

Okay now that sounds like a fix! I do wonder why the native player is able to handle the malformed first frame and the web client is not. I’m sure they have different engines or interpreters…. Maybe this is more of a bug report for Chrome.

Sounds like we should be able to put this into a Cron job to run on a nightly basis as well I’ll test this out in a little bit and make myself a little keto snack.

Not sure how accurate this is from Google Gemini, but sort of makes sense.

And yeah, it should be fixed over on the Chromium side. Apparently Firefox doesn’t have this issue.

That would also explain why some other clients still have the issue - I believe some are just web wrappers of some sort, so still using a web based engine under the hood for playback.


AI Overview

An MP4 file’s first frame is typically an I-frame (Intra-coded frame), a full, self-contained image that doesn’t rely on other frames, acting as the crucial starting point for decoding the rest of the video, though some players might skip it or reorder frames, and it’s often the designated “Poster Frame” for thumbnails, which can be a static image or any chosen frame.

Why the First Frame is Special

  • I-frame: To begin playback, the player needs a complete picture, which is an I-frame (or IDR in H.264/H.265), allowing it to start decoding immediately without needing previous frames.
  • Keyframe: I-frames are also known as keyframes; they are essential for seeking (jumping to specific times) and for generating thumbnails.

@Lazarus_Long ok so the stuttering file does NOT have the I frame as the start and the smooth video does….so I agree that is the smoking gun. The script that was in the github isn’t working for me (yet). I might have some syntax issue. I didn’t want to run this against my whole library until I could test it. BUT the script looks like it just logs the problem files and doesn’t fix them. If I could get a fix script going that would be better. This will take some more time.

@Lazarus_Long

OK generated a new script (via Gemini)

REDACTED@REDACTED:/mnt/storage/Share/Movies/Hocus Pocus (1993)$ ./s2.sh Hocus\ Pocus\ (1993)\ Bluray-1080p\ x265\ AAC.mp4
Analyzing: Hocus Pocus (1993) Bluray-1080p x265 AAC.mp4
:cross_mark: Warning: The first frame is a B-frame. This may cause stuttering.
REDACTED@REDACTED:/mnt/storage/Share/Movies/Hocus Pocus (1993)$ ./s2.sh ../Home\ Alone\ (1990)/Home\ Alone\ (1990)\ Bluray-1080p\ x265\ AAC.mp4
Analyzing: ../Home Alone (1990)/Home Alone (1990) Bluray-1080p x265 AAC.mp4
:white_check_mark: Success: The first frame is an I-frame.
REDACTED@REDACTED:/mnt/storage/Share/Movies/Hocus Pocus (1993)$


#!/bin/bash

# Check if a file was provided
if [ -z "$1" ]; then
    echo "Usage: $0 <path_to_video_file>"
    exit 1
fi

FILE="$1"

echo "Analyzing: $FILE"

# Extract the pict_type of the first frame
FIRST_FRAME=$(ffprobe -v error -select_streams v:0 -show_entries frame=pict_type -of default=noprint_wrappers=1:nokey=1 "$FILE" | head -n 1)

if [ "$FIRST_FRAME" == "I" ]; then
    echo "✅ Success: The first frame is an I-frame."
else
    echo "❌ Warning: The first frame is a $FIRST_FRAME-frame. This may cause stuttering."
fi

going to try the suggested fix:

ffmpeg -i input.mp4 -c copy -map 0 output.mp4

or the more forceful:

ffmpeg -ss 00:00:00 -i input.mp4 -c copy -map 0 output.mp4

will report back.

On my sample movie with the issue, neither of those commands worked to fix mine.

Also tried this without success…

ffmpeg -i test.mp4 -c copy output.mkv

MKVMerge can fix it though:

mkvmerge -o output-2.mkv test.mp4

yup neither worked for me as well….going brute force, which I didn’t want to do:

/mnt/storage/Share/Movies/Hocus Pocus (1993)$ ffmpeg -hwaccel qsv -hwaccel_output_format qsv -i “Hocus Pocus (1993) Bluray-1080p x265 AAC-fixed-forced.mp4” -c:v hevc_qsv -preset slow -global_quality 20 -c:a copy “Hocus Pocus (1993) Fixed-QSV.mp4”

Will report back if that worked….it SHOULD as this is just a re-encode….but maybe the start of a script that can be looped through to fix bad files in an entire library.

@Lazarus_Long ok some data:

2 options that both worked:

Fast BUT requires installing mkvmerge

mkvmerge -o “Hocus_Pocus_Fixed.mkv” “Hocus Pocus (1993) Bluray-1080p x265 AAC.mp4”

Slow but doesn’t require any installation:

ffmpeg -hwaccel qsv -hwaccel_output_format qsv -i “Hocus Pocus (1993) Bluray-1080p x265 AAC-fixed-forced.mp4” -c:v hevc_qsv -preset slow -global_quality 20 -c:a copy “Hocus Pocus (1993) Fixed-QSV.mp4”

Now just need to work on a looping script to ID problem files and fix them. I will most likely do it with the mkvmerge as that is so much faster. Taking a break will come back to it later.

Yeah I think MKVMerge is probably the better route. I think using ffmpeg might not be lossless in your script, whereas MKVMerge is (as long as you’re OK with a different container - some people want to stay in MP4).

@Lazarus_Long thanks for finding the issue.

Ok so….I had Gemini write a script and with some back and forth I have something I am happy with and I have tested this in my test environment with a subset of files both good and bad files. The good files are skipped the bad files are fixed.

Fair warning I tested this on a test setup with a few directories some with good files some with bad files. Tested the dry run and the auto-trash options. i have NOT run this on my full library yet though….need to build some courage before I do that.

Share Code Folder

I might update the code at some point

Stutter Fix Library for must updated if I choose to update in the future

Documentation

#!/bin/bash

# --- Configuration ---
SEARCH_DIR="."
LOG_FILE="fix_report.log"

# --- Flag & Argument Handling ---
DRY_RUN=false
AUTO_MODE=false
AUTO_ACTION=""

for arg in "$@"; do
    case $arg in
        --dry-run) DRY_RUN=true ;;
        --auto-trash) AUTO_MODE=true; AUTO_ACTION="2" ;;
        --auto-rename) AUTO_MODE=true; AUTO_ACTION="1" ;;
        --auto-delete) AUTO_MODE=true; AUTO_ACTION="3" ;;
        *)
            # If the argument is a directory, use it as the search path
            if [ -d "$arg" ]; then
                SEARCH_DIR="$arg"
            fi
            ;;
    esac
done

# Clean up path (remove trailing slashes)
SEARCH_DIR=$(echo "$SEARCH_DIR" | sed 's:/*$::')

if [ "$DRY_RUN" = true ]; then
    echo "!!! DRY RUN MODE ENABLED - No changes will be made !!!"
elif [ "$AUTO_MODE" = true ]; then
    echo "!!! AUTOMATIC MODE ENABLED - Action Code: $AUTO_ACTION !!!"
fi

echo "Scanning Path: $SEARCH_DIR"
echo "------------------------------------------------"

# Recursive find
find "$SEARCH_DIR" -type f \( -name "*.mp4" -o -name "*.mkv" \) -print0 | while read -d '' -r FILE; do

    [[ "$FILE" == *".fixed.mkv" ]] && continue
    [[ "$(basename "$FILE")" == .* ]] && continue

    FIRST_FRAME=$(ffprobe -v error -select_streams v:0 -show_entries frame=pict_type -of default=noprint_wrappers=1:nokey=1 "$FILE" | head -n 1)

    if [ "$FIRST_FRAME" != "I" ] && [ ! -z "$FIRST_FRAME" ]; then
        echo "Found Problem: $FILE (Starts with $FIRST_FRAME)"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY RUN] Would fix: $FILE"
            continue
        fi

        OUT_FILE="${FILE%.*}.fixed.mkv"

        if mkvmerge -o "$OUT_FILE" "$FILE" > /dev/null 2>&1; then
            NEW_FRAME=$(ffprobe -v error -select_streams v:0 -show_entries frame=pict_type -of default=noprint_wrappers=1:nokey=1 "$OUT_FILE" | head -n 1)

            # Decide on action
            if [ "$AUTO_MODE" = true ]; then
                CHOICE=$AUTO_ACTION
            else
                echo "__ Fix attempted. New file starts with: $NEW_FRAME"
                echo "Action for $FILE:"
                echo "1) Rename (.old)  2) Trash  3) Delete  4) Skip"
                read -p "Selection: " CHOICE < /dev/tty
            fi

            case $CHOICE in
                1) mv "$FILE" "$FILE.old"; echo "Renamed $FILE" ;;
                2) trash-put "$FILE"; echo "Trashed $FILE" ;;
                3) rm "$FILE"; echo "Deleted $FILE" ;;
                *) echo "Skipping cleanup for $FILE" ;;
            esac
            echo "$(date): Fixed $FILE" >> "$LOG_FILE"
        fi
    fi
done

Fix-Stutter: Media GOP Header Repair Tool

Overview

fix_stutter.sh is a maintenance utility designed for TedFlix2 to resolve video stuttering issues, specifically in web browsers (Chrome/Firefox). It identifies video files that do not start with an I-frame (Keyframe) and repairs them via non-destructive remuxing using mkvmerge.

Prerequisites

The script requires the following tools installed on the host:

  • ffmpeg (specifically ffprobe for frame analysis)

  • mkvtoolnix (for mkvmerge remuxing)

  • trash-cli (optional, for safe file removal)

Bash

  • sudo apt update && sudo apt install ffmpeg mkvtoolnix trash-cli

Usage

The script accepts a target directory as an argument and supports several operational flags. If no directory is provided, it defaults to the current directory (.).

1. Manual/Interactive Mode

Scan a specific folder and prompt for action on every “bad” file found.

Bash

  • ./fix_stutter.sh /mnt/storage/Share/Movies

2. Audit Mode (Dry Run)

Scan the library and log problematic files without making any changes to the filesystem.

Bash

  • ./fix_stutter.sh /mnt/storage/Share/Movies --dry-run

3. Automatic Batch Mode

Process the entire library without user intervention.

  • Auto-Trash: ./fix_stutter.sh /path/to/movies --auto-trash

  • Auto-Rename: ./fix_stutter.sh /path/to/movies --auto-rename

  • Auto-Delete: ./fix_stutter.sh /path/to/movies --auto-delete


Technical Logic

  1. Recursion: Uses find with -print0 to safely traverse subdirectories and handle complex filenames (spaces, parentheses).

  2. Detection: Uses ffprobe to inspect the pict_type of the first frame.

  3. Remuxing: If the first frame is a P or B frame, mkvmerge wraps the stream into a new Matroska container. This rebuilds the index and places a proper I-frame at the start.

  4. Verification: Re-analyzes the new .fixed.mkv. If it begins with an I frame, the fix is verified.


My Infrastructure Impact

SnapRAID & Parity

Replacing files triggers a “Remove” and “Add” event in SnapRAID.

  • Thresholds: Per your snapraid-runner.conf, if more than 5250 files are replaced, the automated sync will abort. You must then run snapraid sync --ignore-deletethreshold manually.

  • Parity Recalculation: Because the file structure has changed, SnapRAID will perform a full parity calculation for the new files.

MergerFS & Storage

Because you use category.create=mfs in your fstab, the new .mkv file may be written to a different physical disk in the pool than the original .mp4, depending on which disk has more free space.

Plex Media Server

Plex may detect the .fixed.mkv as a new version. If you use --auto-trash, the original file is moved to ~/.local/share/Trash, allowing Plex to swap the metadata to the new file during the next library scan.


Log Files

  • fix_report.log: Timestamped summary of all successfully fixed files.