DappMeetingV3 / temp /basic.html
Luisnguyen1
a
8f39e88
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Video Call</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/8.1.2/adapter.min.js"></script>
</head>
<body>
<div class="container">
<h1>Video Call</h1>
<div class="video-grid">
<div>
<h2>Local Video</h2>
<video id="localVideo" autoplay muted playsinline></video>
</div>
<div>
<h2>Remote Video</h2>
<video id="remoteVideo" autoplay playsinline></video>
</div>
</div>
<div class="controls">
<button id="startButton">Start Call</button>
<input id="sessionInput" placeholder="Enter session ID to join">
<button id="joinButton">Join Call</button>
<button id="endButton">End Call</button>
<div>
<label for="sessionId">Session ID:</label>
<input id="sessionId" readonly>
<button id="copyButton">Copy</button>
</div>
</div>
</div>
<script>
const APP_ID = "d8f5af0fe38ea134ebd03e7bf0a8ae14"; // Thay thế bằng APP_ID của bạn
const APP_TOKEN = "3d9031d1c83112f68636517a1b4c765161bf0b7667fe9a665b528f760b1f4f28"; // Thay thế bằng APP_TOKEN của bạn
const API_BASE = `https://rtc.live.cloudflare.com/v1/apps/${APP_ID}`;
let localStream;
let localPeerConnection;
let localSessionId;
let remoteSessionId = null; // Thêm biến để lưu session id của người tham gia
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
const startButton = document.getElementById('startButton');
const endButton = document.getElementById('endButton');
const sessionInput = document.getElementById('sessionInput');
const joinButton = document.getElementById('joinButton');
const sessionIdInput = document.getElementById('sessionId');
const copyButton = document.getElementById('copyButton');
startButton.onclick = startCall;
endButton.onclick = endCall;
joinButton.onclick = joinCall;
copyButton.onclick = () => {
sessionIdInput.select();
document.execCommand('copy');
alert('Session ID copied to clipboard');
};
async function createSession() {
const response = await fetch(`${API_BASE}/sessions/new`, {
method: "POST",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
}
}).then(res => res.json());
if (response.errorCode) {
throw new Error(response.errorDescription);
}
return response.sessionId;
}
async function createPeerConnection() {
const peerConnection = new RTCPeerConnection({
iceServers: [{
urls: "stun:stun.cloudflare.com:3478"
}],
bundlePolicy: "max-bundle"
});
// Add ICE candidate handling
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log("New ICE candidate:", event.candidate);
}
};
// Add connection state handling
peerConnection.onconnectionstatechange = (event) => {
console.log("Connection state:", peerConnection.connectionState);
};
// Add track handling
peerConnection.ontrack = (event) => {
console.log("Received remote track:", event);
if (remoteVideo.srcObject !== event.streams[0]) {
remoteVideo.srcObject = event.streams[0];
}
};
return peerConnection;
}
async function startCall() {
try {
// Get local media stream
localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
localVideo.srcObject = localStream;
// Create new session
localSessionId = await createSession();
localPeerConnection = await createPeerConnection();
// Add tracks using addTransceiver API like in demo.html
const transceivers = localStream.getTracks().map(track =>
localPeerConnection.addTransceiver(track, {
direction: "sendonly"
})
);
// Create and set local offer
const offer = await localPeerConnection.createOffer();
await localPeerConnection.setLocalDescription(offer);
// Format request according to API schema
const requestBody = {
sessionDescription: {
sdp: offer.sdp,
type: "offer"
},
tracks: transceivers.map(({mid, sender}) => ({
location: "local",
mid: mid,
trackName: sender.track?.id
}))
};
// Set up ICE connection state handler
const connected = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("ICE connection timeout")), 5000);
const iceConnectionStateChangeHandler = () => {
if (localPeerConnection.iceConnectionState === "connected") {
localPeerConnection.removeEventListener(
"iceconnectionstatechange",
iceConnectionStateChangeHandler
);
resolve();
}
};
localPeerConnection.addEventListener(
"iceconnectionstatechange",
iceConnectionStateChangeHandler
);
});
// Send tracks to API
const response = await fetch(`${API_BASE}/sessions/${localSessionId}/tracks/new`, {
method: "POST",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const responseData = await response.json();
// Set remote description from response
await localPeerConnection.setRemoteDescription(
new RTCSessionDescription(responseData.sessionDescription)
);
// Wait for ICE connection
await connected;
// Update UI
sessionIdInput.value = localSessionId;
startSessionCheck(localSessionId);
} catch (err) {
console.error("Error starting call:", err);
alert("Error starting call: " + err.message);
}
}
async function joinCall() {
try {
const sessionToJoin = sessionInput.value.trim();
if (!sessionToJoin) {
alert('Please enter a session ID');
return;
}
// Get session state first
const sessionState = await fetch(`${API_BASE}/sessions/${sessionToJoin}`, {
headers: {
"Authorization": `Bearer ${APP_TOKEN}`
}
}).then(res => res.json());
console.log("Session state:", sessionState);
if (!sessionState.tracks || sessionState.tracks.length === 0) {
throw new Error("No active tracks in session");
}
// Initialize local resources
localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
localVideo.srcObject = localStream;
localPeerConnection = await createPeerConnection();
localSessionId = await createSession();
// First add our local tracks
const localTransceivers = localStream.getTracks().map(track =>
localPeerConnection.addTransceiver(track, {
direction: "sendonly"
})
);
// Create and set local offer for our tracks
const localOffer = await localPeerConnection.createOffer();
await localPeerConnection.setLocalDescription(localOffer);
// Send our tracks first
const addLocalTracksResponse = await fetch(`${API_BASE}/sessions/${localSessionId}/tracks/new`, {
method: "POST",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
sessionDescription: {
sdp: localOffer.sdp,
type: "offer"
},
tracks: localTransceivers.map(({mid, sender}) => ({
location: "local",
mid: mid,
trackName: sender.track?.id
}))
})
}).then(res => res.json());
// Handle local tracks response
await localPeerConnection.setRemoteDescription(
new RTCSessionDescription(addLocalTracksResponse.sessionDescription)
);
// Now pull the remote tracks
const pullRequest = {
tracks: sessionState.tracks
.filter(track => track.status === 'active')
.map(track => ({
location: "remote",
sessionId: sessionToJoin,
trackName: track.trackName
}))
};
// Set up track handling for remote tracks
const resolvingTracks = new Promise((resolve, reject) => {
let receivedTracks = [];
const timeout = setTimeout(() => reject(new Error("Track timeout")), 10000);
const handleTrack = (event) => {
console.log("Received track:", event.track);
receivedTracks.push(event.track);
if (receivedTracks.length === pullRequest.tracks.length) {
clearTimeout(timeout);
localPeerConnection.removeEventListener('track', handleTrack);
resolve(receivedTracks);
}
};
localPeerConnection.addEventListener('track', handleTrack);
});
// Pull remote tracks
const pullResponse = await fetch(`${API_BASE}/sessions/${localSessionId}/tracks/new`, {
method: "POST",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify(pullRequest)
}).then(res => res.json());
// Handle remote tracks
if (pullResponse.requiresImmediateRenegotiation) {
await localPeerConnection.setRemoteDescription(
new RTCSessionDescription(pullResponse.sessionDescription)
);
const answer = await localPeerConnection.createAnswer();
await localPeerConnection.setLocalDescription(answer);
await fetch(`${API_BASE}/sessions/${localSessionId}/renegotiate`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
sessionDescription: {
sdp: answer.sdp,
type: "answer"
}
})
}).then(res => res.json());
}
// Wait for and display remote tracks
const pulledTracks = await resolvingTracks;
const remoteStream = new MediaStream();
pulledTracks.forEach(track => remoteStream.addTrack(track));
remoteVideo.srcObject = remoteStream;
startSessionCheck(localSessionId);
startSessionCheck(sessionToJoin);
} catch (err) {
console.error("Error joining call:", err);
alert("Error joining call: " + err.message);
}
}
async function endCall() {
try {
if (localSessionId) {
// Close tracks following schema
await fetch(`${API_BASE}/sessions/${localSessionId}/tracks/close`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
tracks: [], // Close all tracks
force: true // Force close without renegotiation
})
});
}
// Clean up resources
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
}
if (localPeerConnection) {
localPeerConnection.close();
}
localVideo.srcObject = null;
remoteVideo.srcObject = null;
sessionIdInput.value = '';
sessionInput.value = '';
} catch (err) {
console.error("Error ending call:", err);
}
}
// Sửa lại hàm startSessionCheck để thêm xử lý khi có người tham gia
function startSessionCheck(sessionId) {
const checkInterval = setInterval(async () => {
try {
const response = await fetch(`${API_BASE}/sessions/${sessionId}`, {
headers: {
"Authorization": `Bearer ${APP_TOKEN}`
}
}).then(res => res.json());
if (response.errorCode) {
clearInterval(checkInterval);
console.log(`Session ${sessionId} ended`);
return;
}
// Kiểm tra nếu là session gốc và có tracks mới
if (sessionId === localSessionId && response.tracks) {
const newTracks = response.tracks.filter(track =>
track.status === 'active' &&
track.sessionId !== localSessionId &&
track.sessionId !== remoteSessionId
);
if (newTracks.length > 0) {
console.log("New participant joined, pulling their tracks");
remoteSessionId = newTracks[0].sessionId;
await pullRemoteTracks(newTracks);
}
}
} catch (err) {
clearInterval(checkInterval);
console.error("Session check failed:", err);
}
}, 5000);
}
// Thêm hàm mới để pull tracks từ người tham gia
async function pullRemoteTracks(tracks) {
try {
// Set up track handling
const resolvingTracks = new Promise((resolve, reject) => {
let receivedTracks = [];
const timeout = setTimeout(() => reject(new Error("Track timeout")), 10000);
const handleTrack = (event) => {
console.log("Received remote participant track:", event.track);
receivedTracks.push(event.track);
if (receivedTracks.length === tracks.length) {
clearTimeout(timeout);
localPeerConnection.removeEventListener('track', handleTrack);
resolve(receivedTracks);
}
};
localPeerConnection.addEventListener('track', handleTrack);
});
// Pull remote tracks
const pullResponse = await fetch(`${API_BASE}/sessions/${localSessionId}/tracks/new`, {
method: "POST",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
tracks: tracks.map(track => ({
location: "remote",
sessionId: track.sessionId,
trackName: track.trackName
}))
})
}).then(res => res.json());
if (pullResponse.requiresImmediateRenegotiation) {
await localPeerConnection.setRemoteDescription(
new RTCSessionDescription(pullResponse.sessionDescription)
);
const answer = await localPeerConnection.createAnswer();
await localPeerConnection.setLocalDescription(answer);
await fetch(`${API_BASE}/sessions/${localSessionId}/renegotiate`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${APP_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
sessionDescription: {
sdp: answer.sdp,
type: "answer"
}
})
}).then(res => res.json());
}
// Wait for and display remote tracks
const pulledTracks = await resolvingTracks;
const remoteStream = remoteVideo.srcObject instanceof MediaStream ?
remoteVideo.srcObject : new MediaStream();
pulledTracks.forEach(track => remoteStream.addTrack(track));
remoteVideo.srcObject = remoteStream;
} catch (err) {
console.error("Error pulling remote tracks:", err);
}
}
</script>
<style>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.video-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 20px 0;
}
video {
width: 100%;
background: #333;
}
.controls {
text-align: center;
}
button {
padding: 10px 20px;
margin: 0 10px;
}
.controls div {
margin-top: 10px;
}
#sessionId {
width: 200px;
margin-right: 10px;
}
</style>
</body>
</html>