mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-09 15:38:44 +00:00
update
This commit is contained in:
3
src/demo/config.json
Normal file
3
src/demo/config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"torrentId": "magnet:?xt=urn:btih:a92964583d6bd03b5a420a474a96f2b47d19fd43&dn=SeedSigner_SingleSig_Guide.mp4&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com"
|
||||
}
|
70
src/demo/css/style.css
Normal file
70
src/demo/css/style.css
Normal file
@@ -0,0 +1,70 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 0 20px;
|
||||
background: #1a1a1a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 20px 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
background: #2a2a2a;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 15px 0;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background: #3a3a3a;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
background: #2196F3;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #2196F3;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.peers {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.speed {
|
||||
color: #4CAF50;
|
||||
}
|
32
src/demo/demo.html
Normal file
32
src/demo/demo.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebTorrent Video Demo</title>
|
||||
<link rel="stylesheet" href="./css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebTorrent Video Demo</h1>
|
||||
|
||||
<!-- Video player -->
|
||||
<video id="video" controls></video>
|
||||
|
||||
<!-- Info Container -->
|
||||
<div class="info-container">
|
||||
<div class="status" id="status">Initializing...</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar-fill" id="progress"></div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="peers">
|
||||
<span id="peers">Peers: 0</span>
|
||||
<span class="speed" id="speed">0 KB/s</span>
|
||||
</div>
|
||||
<span id="downloaded">0 MB / 0 MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
296
src/demo/js/main.js
Normal file
296
src/demo/js/main.js
Normal file
@@ -0,0 +1,296 @@
|
||||
import WebTorrent from 'https://esm.sh/webtorrent'
|
||||
|
||||
const client = new WebTorrent()
|
||||
|
||||
function log(msg) {
|
||||
console.log(msg)
|
||||
}
|
||||
|
||||
// Check if running in Brave browser
|
||||
async function isBrave() {
|
||||
return (navigator.brave?.isBrave && await navigator.brave.isBrave()) || false
|
||||
}
|
||||
|
||||
// Longer timeout for Brave
|
||||
const TIMEOUT_DURATION = 60000 // 60 seconds
|
||||
|
||||
const torrentId = 'magnet:?xt=urn:btih:a92964583d6bd03b5a420a474a96f2b47d19fd43&dn=SeedSigner_SingleSig_Guide.mp4&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com'
|
||||
|
||||
async function waitForServiceWorkerActivation(registration) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Service worker activation timeout'))
|
||||
}, TIMEOUT_DURATION)
|
||||
|
||||
log('Waiting for service worker activation...')
|
||||
|
||||
const checkActivation = () => {
|
||||
if (registration.active) {
|
||||
clearTimeout(timeout)
|
||||
log('Service worker is active')
|
||||
resolve(registration)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Check immediately
|
||||
if (checkActivation()) return
|
||||
|
||||
// Set up activation listener
|
||||
registration.addEventListener('activate', () => {
|
||||
checkActivation()
|
||||
})
|
||||
|
||||
// Handle waiting state
|
||||
if (registration.waiting) {
|
||||
log('Service worker is waiting, sending skip waiting message')
|
||||
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
|
||||
}
|
||||
|
||||
// Additional state change listener
|
||||
registration.addEventListener('statechange', () => {
|
||||
checkActivation()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function setupServiceWorker() {
|
||||
try {
|
||||
// Check for service worker support
|
||||
if (!('serviceWorker' in navigator) || !navigator.serviceWorker) {
|
||||
throw new Error('Service Worker not supported or disabled')
|
||||
}
|
||||
|
||||
// Brave-specific initialization
|
||||
if (await isBrave()) {
|
||||
log('Brave browser detected')
|
||||
// Force clear any existing registrations in Brave
|
||||
const registrations = await navigator.serviceWorker.getRegistrations()
|
||||
for (const registration of registrations) {
|
||||
await registration.unregister()
|
||||
}
|
||||
// Add delay for Brave's privacy checks
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
|
||||
// Get current path for service worker scope
|
||||
const currentPath = window.location.pathname
|
||||
const basePath = currentPath.substring(0, currentPath.lastIndexOf('/') + 1)
|
||||
|
||||
// Register service worker with explicit scope
|
||||
log('Registering service worker...')
|
||||
const registration = await navigator.serviceWorker.register('./sw.min.js', {
|
||||
scope: basePath,
|
||||
updateViaCache: 'none'
|
||||
})
|
||||
log('Service worker registered')
|
||||
|
||||
// Wait for installation
|
||||
if (registration.installing) {
|
||||
log('Waiting for installation...')
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Installation timeout'))
|
||||
}, TIMEOUT_DURATION)
|
||||
|
||||
registration.installing.addEventListener('statechange', (e) => {
|
||||
log('Service worker state:', e.target.state)
|
||||
if (e.target.state === 'activated' || e.target.state === 'redundant') {
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for activation
|
||||
await waitForServiceWorkerActivation(registration)
|
||||
log('Service worker activated')
|
||||
|
||||
// Wait for ready state
|
||||
const readyRegistration = await Promise.race([
|
||||
navigator.serviceWorker.ready,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Service worker ready timeout')), TIMEOUT_DURATION)
|
||||
)
|
||||
])
|
||||
|
||||
if (!readyRegistration.active) {
|
||||
throw new Error('Service worker not active after ready state')
|
||||
}
|
||||
|
||||
log('Service worker ready')
|
||||
return registration
|
||||
} catch (error) {
|
||||
log('Service worker setup error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`
|
||||
}
|
||||
|
||||
function startTorrent() {
|
||||
log('Starting torrent download')
|
||||
client.add(torrentId, torrent => {
|
||||
const status = document.querySelector('#status')
|
||||
const progress = document.querySelector('#progress')
|
||||
const video = document.querySelector('#video')
|
||||
const peers = document.querySelector('#peers')
|
||||
const speed = document.querySelector('#speed')
|
||||
const downloaded = document.querySelector('#downloaded')
|
||||
|
||||
log('Torrent added: ' + torrent.name)
|
||||
status.textContent = `Loading ${torrent.name}...`
|
||||
|
||||
const file = torrent.files.find(file => file.name.endsWith('.mp4'))
|
||||
if (!file) {
|
||||
log('No MP4 file found in torrent')
|
||||
status.textContent = 'Error: No video file found'
|
||||
return
|
||||
}
|
||||
|
||||
// Set up video element
|
||||
video.muted = true
|
||||
video.crossOrigin = 'anonymous'
|
||||
|
||||
// Enhanced video error handling
|
||||
video.addEventListener('error', (e) => {
|
||||
const error = e.target.error
|
||||
log('Video error:', error)
|
||||
if (error) {
|
||||
log('Error code:', error.code)
|
||||
log('Error message:', error.message)
|
||||
}
|
||||
status.textContent = 'Error playing video. Try disabling Brave Shields for this site.'
|
||||
})
|
||||
|
||||
video.addEventListener('canplay', () => {
|
||||
const playPromise = video.play()
|
||||
if (playPromise !== undefined) {
|
||||
playPromise
|
||||
.then(() => log('Autoplay started'))
|
||||
.catch(err => {
|
||||
log('Autoplay failed:', err)
|
||||
status.textContent = 'Click to play video'
|
||||
// Add click-to-play handler
|
||||
video.addEventListener('click', () => {
|
||||
video.play()
|
||||
.then(() => log('Play started by user'))
|
||||
.catch(err => log('Play failed:', err))
|
||||
}, { once: true })
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Handle metadata loading
|
||||
video.addEventListener('loadedmetadata', () => {
|
||||
log('Video metadata loaded')
|
||||
if (video.duration === Infinity || isNaN(video.duration)) {
|
||||
log('Invalid duration, attempting to fix...')
|
||||
video.currentTime = 1e101
|
||||
video.currentTime = 0
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
file.streamTo(video)
|
||||
log('Streaming started')
|
||||
} catch (error) {
|
||||
log('Streaming error:', error)
|
||||
status.textContent = 'Error starting video stream'
|
||||
}
|
||||
|
||||
// Update stats every second
|
||||
const statsInterval = setInterval(() => {
|
||||
if (!document.body.contains(video)) {
|
||||
clearInterval(statsInterval)
|
||||
return
|
||||
}
|
||||
|
||||
const percentage = torrent.progress * 100
|
||||
progress.style.width = `${percentage}%`
|
||||
peers.textContent = `Peers: ${torrent.numPeers}`
|
||||
speed.textContent = `${formatBytes(torrent.downloadSpeed)}/s`
|
||||
downloaded.textContent = `${formatBytes(torrent.downloaded)} / ${formatBytes(torrent.length)}`
|
||||
|
||||
if (torrent.progress === 1) {
|
||||
status.textContent = `${torrent.name}`
|
||||
} else {
|
||||
status.textContent = `Loading ${torrent.name}...`
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
torrent.on('error', err => {
|
||||
log('Torrent error:', err)
|
||||
status.textContent = 'Error loading video'
|
||||
clearInterval(statsInterval)
|
||||
})
|
||||
|
||||
// Cleanup handler
|
||||
window.addEventListener('beforeunload', () => {
|
||||
clearInterval(statsInterval)
|
||||
client.destroy()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const isBraveBrowser = await isBrave()
|
||||
|
||||
// Check for secure context
|
||||
if (!window.isSecureContext) {
|
||||
throw new Error('HTTPS or localhost required')
|
||||
}
|
||||
|
||||
// Brave-specific checks
|
||||
if (isBraveBrowser) {
|
||||
log('Checking Brave configuration...')
|
||||
|
||||
// Check if service workers are enabled
|
||||
if (!navigator.serviceWorker) {
|
||||
throw new Error('Please enable Service Workers in Brave Shield settings')
|
||||
}
|
||||
|
||||
// Check for WebRTC
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
throw new Error('Please enable WebRTC in Brave Shield settings')
|
||||
}
|
||||
}
|
||||
|
||||
log('Setting up service worker...')
|
||||
const registration = await setupServiceWorker()
|
||||
|
||||
if (!registration || !registration.active) {
|
||||
throw new Error('Service worker setup failed')
|
||||
}
|
||||
|
||||
log('Service worker activated and ready')
|
||||
|
||||
// Create WebTorrent server with activated service worker
|
||||
client.createServer({ controller: registration })
|
||||
log('WebTorrent server created')
|
||||
|
||||
// Start the torrent
|
||||
startTorrent()
|
||||
} catch (error) {
|
||||
log('Initialization error:', error)
|
||||
const status = document.querySelector('#status')
|
||||
if (status) {
|
||||
const errorMessage = await isBrave()
|
||||
? `${error.message} (Try disabling Brave Shields for this site)`
|
||||
: error.message
|
||||
status.textContent = 'Error initializing: ' + errorMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start everything when page loads
|
||||
window.addEventListener('load', init)
|
132
src/demo/sw.min.js
vendored
Normal file
132
src/demo/sw.min.js
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
// Handle skip waiting message
|
||||
self.addEventListener('message', event => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting()
|
||||
}
|
||||
})
|
||||
|
||||
// Immediately install and activate
|
||||
self.addEventListener("install", () => {
|
||||
self.skipWaiting()
|
||||
})
|
||||
|
||||
// Claim clients on activation
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
clients.claim(),
|
||||
self.skipWaiting(),
|
||||
caches.keys().then(cacheNames =>
|
||||
Promise.all(cacheNames.map(cacheName => caches.delete(cacheName)))
|
||||
)
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// Handle fetch events
|
||||
self.addEventListener("fetch", s => {
|
||||
const t = (s => {
|
||||
const { url: t } = s.request;
|
||||
|
||||
// Only handle webtorrent requests
|
||||
if (!t.includes(self.registration.scope + "webtorrent/")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle keepalive requests
|
||||
if (t.includes(self.registration.scope + "webtorrent/keepalive/")) {
|
||||
return new Response();
|
||||
}
|
||||
|
||||
// Handle cancel requests
|
||||
if (t.includes(self.registration.scope + "webtorrent/cancel/")) {
|
||||
return new Response(new ReadableStream({
|
||||
cancel() {
|
||||
cancelled = true;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Handle streaming requests
|
||||
return async function({ request: s }) {
|
||||
const { url: t, method: n, headers: o, destination: a } = s;
|
||||
|
||||
// Get all window clients
|
||||
const l = await clients.matchAll({
|
||||
type: "window",
|
||||
includeUncontrolled: true
|
||||
});
|
||||
|
||||
// Create message channel and wait for response
|
||||
const [r, i] = await new Promise(e => {
|
||||
for (const s of l) {
|
||||
const l = new MessageChannel,
|
||||
{ port1: r, port2: i } = l;
|
||||
r.onmessage = ({ data: s }) => {
|
||||
e([s, r])
|
||||
};
|
||||
s.postMessage({
|
||||
url: t,
|
||||
method: n,
|
||||
headers: Object.fromEntries(o.entries()),
|
||||
scope: self.registration.scope,
|
||||
destination: a,
|
||||
type: "webtorrent"
|
||||
}, [i]);
|
||||
}
|
||||
});
|
||||
|
||||
let c = null;
|
||||
|
||||
const d = () => {
|
||||
i.postMessage(false);
|
||||
clearTimeout(c);
|
||||
i.onmessage = null;
|
||||
};
|
||||
|
||||
// Handle non-streaming response
|
||||
if (r.body !== "STREAM") {
|
||||
d();
|
||||
return new Response(r.body, r);
|
||||
}
|
||||
|
||||
// Handle streaming response
|
||||
return new Response(new ReadableStream({
|
||||
pull: s => new Promise(t => {
|
||||
i.onmessage = ({ data: e }) => {
|
||||
if (e) {
|
||||
s.enqueue(e);
|
||||
} else {
|
||||
d();
|
||||
s.close();
|
||||
}
|
||||
t();
|
||||
};
|
||||
|
||||
if (!cancelled && a !== "document") {
|
||||
clearTimeout(c);
|
||||
c = setTimeout(() => {
|
||||
d();
|
||||
t();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
i.postMessage(true);
|
||||
}),
|
||||
cancel() {
|
||||
d();
|
||||
}
|
||||
}), r);
|
||||
}(s);
|
||||
})(s);
|
||||
|
||||
if (t) {
|
||||
s.respondWith(t);
|
||||
}
|
||||
});
|
||||
})();
|
Reference in New Issue
Block a user