mirror of
git://f0xx.org/ac/ac-be-issues
synced 2026-07-29 05:19:06 +03:00
initial
This commit is contained in:
224
public/assets/js/live_join.js
Normal file
224
public/assets/js/live_join.js
Normal file
@@ -0,0 +1,224 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function basePath() {
|
||||
return document.body.getAttribute('data-base-path') || '';
|
||||
}
|
||||
|
||||
function el(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function detectPlatform() {
|
||||
var ua = navigator.userAgent || '';
|
||||
if (/Android/i.test(ua)) return 'android';
|
||||
if (/iPhone|iPad|iPod/i.test(ua)) return 'ios';
|
||||
if (/Firefox\//i.test(ua)) return 'firefox';
|
||||
if (/Edg\//i.test(ua)) return 'edge';
|
||||
if (/Chrome\//i.test(ua)) return 'chrome';
|
||||
if (/Safari\//i.test(ua)) return 'safari';
|
||||
return 'desktop';
|
||||
}
|
||||
|
||||
function anonId() {
|
||||
var key = 'ac_live_anon_id';
|
||||
try {
|
||||
var existing = localStorage.getItem(key);
|
||||
if (existing) return existing;
|
||||
var id = 'anon-' + Math.random().toString(36).slice(2) + Date.now().toString(36);
|
||||
localStorage.setItem(key, id);
|
||||
return id;
|
||||
} catch (e) {
|
||||
return 'anon-' + Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
function apiUrl() {
|
||||
return basePath() + '/api/live_cast.php';
|
||||
}
|
||||
|
||||
function postJoinEvent(sessionId, eventType, direct) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', apiUrl(), true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
xhr.send(JSON.stringify({
|
||||
action: 'join_event',
|
||||
session_id: sessionId,
|
||||
event_type: eventType,
|
||||
platform: detectPlatform(),
|
||||
channel: direct ? 'direct_link' : 'viewer_link',
|
||||
direct: !!direct,
|
||||
anon_id: anonId()
|
||||
}));
|
||||
}
|
||||
|
||||
function fetchSession(sessionId, cb) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', apiUrl() + '?action=session&session_id=' + encodeURIComponent(sessionId), true);
|
||||
xhr.onload = function () {
|
||||
var payload = null;
|
||||
try {
|
||||
payload = JSON.parse(xhr.responseText);
|
||||
} catch (e) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
cb(payload && payload.ok ? payload.session : null);
|
||||
};
|
||||
xhr.onerror = function () { cb(null); };
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function formatStatus(session) {
|
||||
if (!session) return 'Unknown';
|
||||
var st = session.status || '';
|
||||
if (st === 'live' || st === 'intent') return 'Live';
|
||||
if (st === 'closed') return 'Ended';
|
||||
if (st === 'expired') return 'Expired';
|
||||
return st;
|
||||
}
|
||||
|
||||
function renderStats(statsEl, stats) {
|
||||
if (!statsEl || !stats) return;
|
||||
statsEl.hidden = false;
|
||||
statsEl.innerHTML =
|
||||
'<strong>Statistics for nerds</strong><br>'
|
||||
+ 'ICE: ' + escapeHtml(stats.ice_state || '—')
|
||||
+ ' · sig: ' + escapeHtml(stats.signaling_state || '—')
|
||||
+ '<br>In: ' + (stats.inbound_kbps || 0) + ' kbps · Out: ' + (stats.outbound_kbps || 0) + ' kbps'
|
||||
+ '<br>RTT: ' + (stats.rtt_ms || 0) + ' ms · frames: ' + (stats.frames_decoded || 0)
|
||||
+ (stats.frame_w ? '<br>Frame: ' + stats.frame_w + '×' + stats.frame_h : '');
|
||||
}
|
||||
|
||||
function startViewer(sessionId, session, playerEl, statusEl, statsEl) {
|
||||
if (!window.RTCPeerConnection || !window.LiveCastWebRtc) {
|
||||
playerEl.innerHTML = '<p class="muted">WebRTC is not available in this browser.</p>';
|
||||
return null;
|
||||
}
|
||||
playerEl.innerHTML =
|
||||
'<video id="live-remote-video" class="live-preview" playsinline autoplay controls></video>'
|
||||
+ '<div id="live-join-stats" class="live-stats-nerds" hidden></div>';
|
||||
var video = el('live-remote-video');
|
||||
var nerds = el('live-join-stats');
|
||||
var conn = window.LiveCastWebRtc.connect({
|
||||
basePath: basePath(),
|
||||
sessionId: sessionId,
|
||||
role: 'viewer',
|
||||
onStatus: function (msg) {
|
||||
statusEl.textContent = formatStatus(session) + ' — ' + msg;
|
||||
},
|
||||
onRemoteStream: function (stream) {
|
||||
if (video) {
|
||||
video.srcObject = stream;
|
||||
video.play().catch(function () {});
|
||||
}
|
||||
statusEl.textContent = formatStatus(session) + ' — playing stream';
|
||||
},
|
||||
onStats: function (stats) {
|
||||
renderStats(nerds || statsEl, stats);
|
||||
}
|
||||
});
|
||||
if (conn && video) conn.setRemoteVideo(video);
|
||||
return conn;
|
||||
}
|
||||
|
||||
function boot() {
|
||||
var root = el('live-join-app');
|
||||
if (!root) return;
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var sessionId = params.get('session_id') || root.getAttribute('data-session-id') || '';
|
||||
var direct = params.get('direct') === '1' || root.getAttribute('data-direct') === '1';
|
||||
var role = params.get('role') || (direct ? 'direct' : 'viewer');
|
||||
var webrtcConn = null;
|
||||
|
||||
if (!sessionId) {
|
||||
el('live-join-status').textContent = 'Missing session_id in URL.';
|
||||
return;
|
||||
}
|
||||
|
||||
postJoinEvent(sessionId, 'open', direct);
|
||||
|
||||
var statusEl = el('live-join-status');
|
||||
var metaEl = el('live-join-meta');
|
||||
var playerEl = el('live-join-player');
|
||||
var statsEl = el('live-join-stats-panel');
|
||||
|
||||
function render(session) {
|
||||
if (!session) {
|
||||
statusEl.textContent = 'Session not found or no longer available.';
|
||||
return;
|
||||
}
|
||||
var live = session.status === 'live' || session.status === 'intent';
|
||||
statusEl.textContent = formatStatus(session) + (live ? ' — connecting…' : '');
|
||||
var owner = session.owner_username || 'host';
|
||||
var lines = [
|
||||
'Host: ' + owner,
|
||||
'Platform: ' + (session.platform || '—'),
|
||||
'Video: ' + (session.codec_video || '—'),
|
||||
'Audio: ' + (session.codec_audio || '—'),
|
||||
'Bandwidth: ' + (session.bw_mode || '—')
|
||||
];
|
||||
if (session.join_short_url) {
|
||||
lines.push('Viewer link: ' + session.join_short_url);
|
||||
}
|
||||
metaEl.innerHTML = lines.map(function (l) {
|
||||
return '<li>' + escapeHtml(l) + '</li>';
|
||||
}).join('');
|
||||
|
||||
if (direct && /android|ios/i.test(detectPlatform())) {
|
||||
playerEl.innerHTML =
|
||||
'<p class="muted">Open in the AndroidCast app for direct P2P on LAN.</p>' +
|
||||
'<p><code>androidcast://join?session_id=' + escapeHtml(sessionId) + '&direct=1</code></p>' +
|
||||
'<p class="muted">Mobile WebRTC signaling uses the same REST API when the app transport is wired.</p>';
|
||||
postJoinEvent(sessionId, 'join', true);
|
||||
} else if (live) {
|
||||
webrtcConn = startViewer(sessionId, session, playerEl, statusEl, statsEl);
|
||||
postJoinEvent(sessionId, 'join', false);
|
||||
} else {
|
||||
playerEl.innerHTML = '<p class="muted">Session is not live.</p>';
|
||||
}
|
||||
|
||||
if (window.AndroidCastAnalytics && window.AndroidCastAnalytics.trackEvent) {
|
||||
window.AndroidCastAnalytics.trackEvent('live_join_view', {
|
||||
session_id: sessionId,
|
||||
direct: direct ? 1 : 0,
|
||||
platform: detectPlatform(),
|
||||
status: session.status || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchSession(sessionId, render);
|
||||
setInterval(function () {
|
||||
fetchSession(sessionId, function (session) {
|
||||
if (!session) return;
|
||||
if (!(session.status === 'live' || session.status === 'intent')) {
|
||||
statusEl.textContent = formatStatus(session);
|
||||
if (webrtcConn) {
|
||||
webrtcConn.stop();
|
||||
webrtcConn = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
if (webrtcConn) webrtcConn.stop();
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user