簡(jiǎn)單的端到端音視頻

index.html

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>WebRTC 通話(huà)</title>
</head>
<body>
    <button id="startCall">發(fā)起通話(huà)</button>
    <button id="endCall" disabled>掛斷通話(huà)</button>
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>

    <script>
        let localStream;
        let peerConnection;
        const signalingServer = new WebSocket('ws://localhost:8080/ws');

        // 獲取本地媒體流
        async function getMedia() {
            try {
                localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
                document.getElementById('localVideo').srcObject = localStream;
            } catch (err) {
                console.error('獲取媒體設(shè)備失敗:', err);
            }
        }

        // 初始化 PeerConnection
        function createPeerConnection() {
            const config = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
            peerConnection = new RTCPeerConnection(config);

            // 添加本地流
            localStream.getTracks().forEach(track => {
                peerConnection.addTrack(track, localStream);
            });

            // 處理 ICE 候選
            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    signalingServer.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
                }
            };

            // 處理遠(yuǎn)程流
            peerConnection.ontrack = (event) => {
                document.getElementById('remoteVideo').srcObject = event.streams[0];
            };
        }

        // 發(fā)起通話(huà)按鈕
        document.getElementById('startCall').addEventListener('click', async () => {
            const offer = await peerConnection.createOffer();
            await peerConnection.setLocalDescription(offer);
            signalingServer.send(JSON.stringify({ type: 'offer', offer }));
            document.getElementById('startCall').disabled = true;
            document.getElementById('endCall').disabled = false;
        });

        // 掛斷按鈕
        document.getElementById('endCall').addEventListener('click', () => {
            peerConnection.close();
            peerConnection = null;
            document.getElementById('startCall').disabled = false;
            document.getElementById('endCall').disabled = true;
            document.getElementById('remoteVideo').srcObject = null;
        });

        // 處理信令消息
        signalingServer.onmessage = async (event) => {
            const message = JSON.parse(event.data);
            if (!peerConnection) createPeerConnection();

            switch (message.type) {
                case 'offer':
                    await peerConnection.setRemoteDescription(message.offer);
                    const answer = await peerConnection.createAnswer();
                    await peerConnection.setLocalDescription(answer);
                    signalingServer.send(JSON.stringify({ type: 'answer', answer }));
                    break;
                case 'answer':
                    await peerConnection.setRemoteDescription(message.answer);
                    break;
                case 'candidate':
                    await peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
                    break;
            }
        };
        async function start() {
            await getMedia();
            createPeerConnection();
        }
        start();
    </script>
</body>
</html>

main.go

// server.go
package main

import (
    "github.com/gorilla/websocket"
    "log"
    "net/http"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
}

type Client struct {
    conn *websocket.Conn
    send chan []byte
}

var clients = make(map[*Client]bool)
var broadcast = make(chan []byte)

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("WebSocket upgrade error:", err)
        return
    }
    defer conn.Close()

    client := &Client{conn: conn, send: make(chan []byte, 256)}
    clients[client] = true

    // 讀取消息并廣播給其他客戶(hù)端
    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            delete(clients, client)
            break
        }
        // 將消息廣播給所有其他客戶(hù)端
        for otherClient := range clients {
            if otherClient != client {
                otherClient.conn.WriteMessage(websocket.TextMessage, message)
            }
        }
    }
}

func main() {
    http.HandleFunc("/ws", handleWebSocket)
    log.Println("信令服務(wù)器運(yùn)行在 :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容