webrtc浏览器端直播推流示例代码

代码语言:html

所属分类:多媒体

代码描述:webrtc浏览器端直播推流示例代码,直播服务器可以安装mediamtx,地址https://github.com/bluenviron/mediamtx

代码标签: webrtc 浏览器 直播 推流 示例 代码

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<style>
html, body {
	margin: 0;
	padding: 0;
	height: 100%;
	overflow: hidden;
}
body {
    display: flex;
    flex-direction: column;
}
#video {
	height: 100%;
	background: black;
    flex-grow: 1;
    min-height: 0;
}
#controls {
    height: 200px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
#device {
    flex-direction: column;
}
#device > div {
    margin: 10px 0;
    display: flex;
    gap: 20px;
    justify-content: center;
}
select {
    width: 200px;
}
</style>
</head>
<body>

<video id="video" muted controls autoplay playsinline></video>
<div id="controls">
    <div id="initializing" style="display: block;">
        initializing
    </div>
    <div id="device" style="display: none;">
        <div id="device_line">
            video device:
            <select id="video_device">
                <option value="none">none</option>
            </select>

            audio device:
            <select id="audio_device">
                <option value="none">none</option>
            </select>
        </div>
        <div id="codec_line">
            video codec:
            <select id="video_codec">
            </select>

            audio codec:
            <select id="audio_codec">
            </select>
        </div>
        <div id="bitrate_line">
            video bitrate (kbps):
            <input id="video_bitrate" type="text" value="10000" />

            audio bitrate (kbps):
            <input id="audio_bitrate" type="text" value="32" />

            <div>
                <input id="audio_voice" type="checkbox" checked>
                <label for="audio_voice">optimize for voice</label>
            </div>
        </div>
        <div id="submit_line">
            <button id="publish_confirm">publish</button>
        </div>
    </div>
    <div id="transmitting" style="display: none;">
        publishing
    </div>
    <div id="error" style="display: none;">
        <span id="error-message"></span>
    </div>
</div>

<script>

const INITIALIZING = 0;
const DEVICE = 1;
const TRANSMITTING = 2;
const ERROR = 3;

let state = INITIALIZING;
let errorMessage = '';

const render = () => {
    for (const el of ['initializing', 'device', 'transmitting', 'error']) {
        document.getElementById(el).style.display = 'none';
    }

    switch (state) {
    case DEVICE:
        document.getElementById('device').style.display = 'flex';
        break;

    case TRANSMITTING:
        document.getElementById('transmitting').style.display = 'flex';
        break;

    case ERROR:
        document.getElementById('error').style.display = 'flex';
        document.getElementById('error-message').innerHTML = 'error: ' + errorMessage;
        break;
    }
};

const restartPause = 2000;

const unquoteCredential = (v) => (
    JSON.parse(`"${v}"`)
);

const linkToIceServers = (links) => (
    (links !== null) ? links.split(', ').map((link) => {
        const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
        const ret = {
            urls: [m[1]],
        };

        if (m[3] !== undefined) {
            ret.username = unquoteCredential(m[3]);
            ret.credential = unquoteCredential(m[4]);
            ret.credentialType = "password";
        }

        return ret;
    }) : []
);

const parseOffer = (offer) => {
    const ret = {
        iceUfrag: '',
        icePwd: '',
        medias: [],
    };

    for (const line of offer.split('\r\n')) {
        if (line.startsWith('m=')) {
            ret.medias.push(line.slice('m='.length));
        } else if (ret.iceUfrag === '' && line.startsWith('a=ice-ufrag:')) {
            ret.iceUfrag = line.slice('a=ice-ufrag:'.length);
        } else if (ret.icePwd === '' && line.startsWith('a=ice-pwd:')) {
            ret.icePwd = line.slice('a=ice-pwd:'.length);
        }
    }

    return ret;
};

const generateSdpFragment = (offerData, candidates) => {
    const candidatesByMedia = {};
    for (const candidate of candidates) {
        const mid = candidate.sdpMLineIndex;
        if (candidatesByMedia[mid] === undefined) {
            candidatesByMedia[mid] = [];
        }
        candidatesByMedia[mid].push(candidate);
    }

    let frag = 'a=ice-ufrag:' + offerData.iceUfrag + '\r\n'
        + 'a=ice-pwd:' + offerData.icePwd + '\r\n';

    let mid = 0;

    for (const media of offerData.medias) {
        if (candidatesByMedia[mid] !== undefined) {
            frag += 'm=' + media + '\r\n'
                + 'a=mid:' + mid + '\r\n';

            for (const candidate of candidatesByMedia[mid]) {
                frag += 'a=' + candidate.candidate + '\r\n';
            }
        }
        mid++;
    }

    return frag;
};

const setCodec = (section, codec) => {
    const lines = section.split('\r\n');
    const lines2 = [];
    const payloadFormats = [];

    for (const line of lines) {
        if (!line.startsWith('a=rtpmap:')) {
            lines2.push(line);
        } else {
            if (line.toLowerCase().includes(codec)) {
                payloadFormats.push(line.slice('a=rtpmap:'.length).split(' ')[0]);
                lines2.push(line);
            }
        }
    }

    const lines3 = [];

    for (const line of lines2) {
        if (line.startsWith('a=fmtp:')) {
            if (payloadFormats.includes(line.slice('a=fmtp:'.length).split(' ')[0])) {
                lines3.push(line);
            }
        } else if (line.startsWith('a=rtcp-fb:')) {
            if (payloadFormats.includes(line.slice('a=rtcp-fb:'.length).split(' ')[0])) {
                lines3.push(line);
            }
        } else {
            lines3.push(line);
        }
    }

    return lines3.join('\r\n');
};

const setVideoBitrate = (section, bitrate) => {
    let lines = section.split('\r\n');

    for (let i = 0; i < lines.length; i++) {
        if (lines[i].startsWith('c=')) {
            lines = [...lines.slice(0, i+1), 'b=TIAS:' + (parseInt(bitrate) * 1024).toString(), ...lines.slice(i+1)];
            break
        }
    }

    return lines.join('\r\n');
};

const setAudioBitrate = (section, bitrate, voice) => {
    let opusPayloadFormat = '';
    let lines = section.split('\r\n');

    for (let i = 0; i < lines.length; i++) {
        if (lines[i].startsWith('a=rtpmap:') && lines[i].toLowerCase().includes('opus/')) {
            opusPayloadFormat = lines[i].slice('a=rtpmap:'.length).split(' ')[0];
            break;
        }
    }

    if (opusPayloadFormat === '') {
        return section;
    }

    for (let i = 0; i < lines.length; i++) {
        if (lines[i].startsWith('a=fmtp:' + opusPayloadFormat + ' ')) {
            if (voice) {
                lines[i] = 'a=fmtp:' + opusPayloadFormat + ' minptime=10;useinbandfec=1;maxaveragebitrate='
                    + (parseInt(bitrate) * 1024).toString();
            } else {
                lines[i] = 'a=fmtp:' + opusPayloadFormat + ' maxplaybackrate=48000;stereo=1;sprop-stereo=1;maxaveragebitrate'
                    + (parseInt(bitrate) * 1024).toString();
            }
        }
    }

    return lines.join('\r\n');
};

const editAnswer = (answer, videoCodec, audioCodec, videoBitrate, audioBitrate, audioVoice) => {
    const sections = answer.split('m=');

    for (let i = 0; i < sections.length; i++) {
        const section = sections[i];
        if (section.startsWith('video')) {
            sections[i] = setVideoBitrate(setCodec(section, videoCodec), videoBitrate);
        } else if (section.startsWith('audio')) {
            sections[i] = setAudioBitrate(setCodec(section, audioCodec), audioBitrate, audioVoice);
        }
    }

    return sections.join('m=');
};

class Transmitter {
    constructor(stream) {
        this.stream = stream;
		this.pc = null;
		this.restartTimeout = null;
        this.eTag = '';
        this.queuedCandidates = [];
		this.start();
    }

    start() {
        //获取ice服务器,这里可以选择支持webrtc的直播流服务器地址
        console.log("requesting ICE servers");

        fetch(new URL('whip', window.location.href) + window.location.search, {
            method: 'OPTIONS',
        })
            .then((res) => this.onIceServers(res))
            .catch((err) => {
                console.log('error: ' + err);
                this.scheduleRestart();
          .........完整代码请登录后点击上方下载按钮下载查看

网友评论0