You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chrome录制的RecordRTC视频无法在Safari及Chrome iOS播放问题

跨浏览器视频录制与播放解决方案

问题根源

你在MacBook Chrome中录制的视频无法在Safari(Mac/iPhone)和iPhone Chrome中播放,核心原因是Chrome默认使用的VP9编码WebM格式不被Safari支持。Safari对WebM格式的支持有限,尤其不兼容VP9编解码器,而H.264(AVC1)+ AAC是目前跨浏览器兼容性最好的音视频编码组合。

具体修复步骤

1. 替换UA嗅探为特性检测

放弃不可靠的UserAgent判断,直接使用浏览器原生的MediaRecorder.isTypeSupported()方法检测支持的MIME类型,确保选择的格式能被当前浏览器和目标播放环境兼容。

2. 统一使用H.264编码格式

优先选择video/mp4;codecs=avc1,aac(MP4容器+H.264视频+AAC音频),这是所有现代浏览器(包括Safari、Chrome、Firefox)都支持的格式。如果浏览器不支持MP4录制,则降级到video/webm;codecs=h264。

3. 修复代码中的语法错误

saveRecording函数中存在字符串拼接错误:'video/'.videoFormat应改为'video/' + videoFormat,否则会导致上传的文件MIME类型无效。

完整修改后的代码

var startCameraBtn = document.querySelector('#start-camera-btn');
var startRecordingBtn = document.querySelector('#start-recording-btn');
var stopRecordingBtn = document.querySelector('#stop-recording-btn');
var retakeRecordingBtn = document.querySelector('#retake-recording-btn');
var saveRecordingBtn = document.querySelector('#save-recording-btn');
var publishJobBtn = document.querySelector('#publish-job-btn');
var video = document.querySelector('video');

var recorder; // globally accessible
var blob;
var mimeType;
var videoFormat;

// 初始化时检测最佳兼容格式
detectBestVideoFormat();

function captureCamera(callback) {
    navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(function(camera) {
        callback(camera);
        console.log("Camera started");
    }).catch(function(error) {
        alert('无法访问摄像头,请查看控制台日志。');
        console.error(error);
    });
}

function stopRecordingCallback() {
    video.src = video.srcObject = null;
    video.muted = false;
    video.volume = 1;
    blob = recorder.getBlob()
    video.src = URL.createObjectURL(blob);
}

function startCamera(){
    this.style.display = "none";
    saveRecordingBtn.style.display = "none";
    startRecordingBtn.style.display = "block";
    
    captureCamera(function(camera) {
        video.muted = true;
        video.volume = 0;
        video.srcObject = camera;

        recorder = RecordRTC(camera, {
            type: 'video',
            mimeType: mimeType,
            recorderType: MediaStreamRecorder,
            disableLogs: true,
            bitsPerSecond: 128000,
            audioBitsPerSecond: 128000,
            videoBitsPerSecond: 128000
        });

        recorder.camera = camera;
    });
}

function startRecording(){
    this.style.display = "none";
    stopRecordingBtn.style.display = "block";
    recorder.startRecording();
}

function stopRecording(){
    this.style.display = "none";
    retakeRecordingBtn.style.display = "block";
    saveRecordingBtn.style.display = "block";
    recorder.stopRecording(stopRecordingCallback);
}

function saveRecording(){
    this.innerHTML = '保存中...';
    var fileName = getFileName(videoFormat);

    // 修复字符串拼接错误
    var fileObject = new File([blob], fileName, {
         type: 'video/' + videoFormat
    });
    uploadToPHPServer(fileObject, function(response, fileDownloadURL) {
        console.log(response);
    });
}

startCameraBtn.onclick = startCamera;
startRecordingBtn.onclick = startRecording;
stopRecordingBtn.onclick = stopRecording;
retakeRecordingBtn.onclick = startCamera;
saveRecordingBtn.onclick = saveRecording;

function uploadToPHPServer(blob, callback) {
    var formData = new FormData();
    formData.append('video-filename', blob.name);
    formData.append('video-blob', blob);
    callback('正在上传录制文件到服务器。');

    var upload_url = 'upload_video.php';
                
    makeXMLHttpRequest(upload_url, formData, function(progress) {
        if (progress !== 'upload-ended') {
            callback(progress);
            return;
        }
        var initialURL = upload_url + blob.name;
        callback('上传完成', initialURL);
    });
}

function makeXMLHttpRequest(url, data, callback) {
    var request = new XMLHttpRequest();
    request.onreadystatechange = function() {
        if (request.readyState == 4 && request.status == 200) {
            if (request.responseText === 'success') {
                callback('upload-ended');
                return;
            }
            saveRecordingBtn.innerHTML = request.responseText;
            publishJobBtn.style.display = "block";
            return;
        }
    };
    request.upload.onloadstart = function() {
        callback('PHP上传开始...');
    };
    request.upload.onprogress = function(event) {
        callback('PHP上传进度 ' + Math.round(event.loaded / event.total * 100) + "%");
    };
    request.upload.onload = function() {
        callback('PHP上传完成,获取文件URL中。');
    };
    request.upload.onerror = function(error) {
        callback('PHP上传失败。');
    };
    request.upload.onabort = function(error) {
        callback('PHP上传已终止。');
    };
    request.open('POST', url);
    request.send(data);
}

// 替换UA嗅探为特性检测
function detectBestVideoFormat() {
    // 优先检测MP4(H.264+AAC)
    if (MediaRecorder.isTypeSupported('video/mp4;codecs=avc1,aac')) {
        mimeType = 'video/mp4;codecs=avc1,aac';
        videoFormat = 'mp4';
    } 
    // 降级到WebM+H.264
    else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264')) {
        mimeType = 'video/webm;codecs=h264';
        videoFormat = 'webm';
    }
    // 最后的兜底方案
    else {
        mimeType = 'video/webm';
        videoFormat = 'webm';
    }
    console.log("当前使用视频格式: "+videoFormat+" MIME类型: "+mimeType);
}

function getFileName(fileExtension) {
    var d = new Date();
    var year = d.getUTCFullYear();
    var month = d.getUTCMonth();
    var date = d.getUTCDate();
    return 'RecordRTC-' + year + month + date + '-' + getRandomString() + '.' + fileExtension;
}

function getRandomString() {
    if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) {
        var a = window.crypto.getRandomValues(new Uint32Array(3)),
            token = '';
        for (var i = 0, l = a.length; i < l; i++) {
            token += a[i].toString(36);
        }
        return token;
    } else {
        return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
    }
}

额外注意事项

  • 服务器端兼容: 确保你的PHP服务器正确处理MP4文件的MIME类型(video/mp4),避免返回错误的Content-Type导致播放失败。
  • 测试覆盖: 在所有目标浏览器(Mac Chrome/Safari、iPhone Safari/Chrome)中测试录制和播放流程,确保兼容性。

内容的提问来源于stack exchange,提问作者nadwebdev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 06:02:15