Fix several minor UI issues and refactor download / seed logic
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
9053dbfbef
commit
359b01950c
10
src/App.vue
10
src/App.vue
|
@ -1,6 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<Toast position="top-right" />
|
<Toast position="top-right" />
|
||||||
<MenuBar :model="items" />
|
<MenuBar :model="items">
|
||||||
|
<template #end>
|
||||||
|
<div id="menuBarEnd"></div>
|
||||||
|
</template>
|
||||||
|
</MenuBar>
|
||||||
<div class="p-p-6">
|
<div class="p-p-6">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,6 +34,10 @@ export default {
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#menuBarEnd button {
|
||||||
|
width: 8em
|
||||||
|
}
|
||||||
|
|
||||||
a:focus, video:focus {
|
a:focus, video:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
outline: none !important
|
outline: none !important
|
||||||
|
|
|
@ -3,6 +3,15 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
Wire statistics
|
Wire statistics
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="downloadSpeed || uploadSpeed" #subtitle>
|
||||||
|
<template v-if="downloadSpeed">
|
||||||
|
<i class="pi pi-arrow-down"></i> <span>{{downloadSpeed}}</span> |
|
||||||
|
</template>
|
||||||
|
<template v-if="uploadSpeed">
|
||||||
|
<i class="pi pi-arrow-up"></i> <span>{{uploadSpeed}}</span>
|
||||||
|
</template>
|
||||||
|
<Divider />
|
||||||
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<DataTable :value="wireStatistics" :sortField="sortField" :sortOrder="sortOrder" :autoLayout="true">
|
<DataTable :value="wireStatistics" :sortField="sortField" :sortOrder="sortOrder" :autoLayout="true">
|
||||||
<Column field="id" header="Wire ID" :sortable="true" />
|
<Column field="id" header="Wire ID" :sortable="true" />
|
||||||
|
@ -26,18 +35,24 @@ export default {
|
||||||
sortOrder: {
|
sortOrder: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: -1
|
default: -1
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setup () {
|
downloadSpeed: {
|
||||||
const columns = [
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
uploadSpeed: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
{ field: 'remoteAddress', header: 'Remote address' },
|
{ field: 'remoteAddress', header: 'Remote address' },
|
||||||
{ field: 'uploadSpeed', header: 'Upload speed' },
|
|
||||||
{ field: 'uploaded', header: 'Uploaded' },
|
|
||||||
{ field: 'downloadSpeed', header: 'Download speed' },
|
{ field: 'downloadSpeed', header: 'Download speed' },
|
||||||
{ field: 'downloaded', header: 'Downloaded' }
|
{ field: 'downloaded', header: 'Downloaded' },
|
||||||
|
{ field: 'uploadSpeed', header: 'Upload speed' },
|
||||||
|
{ field: 'uploaded', header: 'Uploaded' }
|
||||||
]
|
]
|
||||||
return {
|
|
||||||
columns
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/main.js
11
src/main.js
|
@ -12,6 +12,7 @@ import Button from 'primevue/button'
|
||||||
import Card from 'primevue/card'
|
import Card from 'primevue/card'
|
||||||
import Column from 'primevue/column'
|
import Column from 'primevue/column'
|
||||||
import DataTable from 'primevue/datatable'
|
import DataTable from 'primevue/datatable'
|
||||||
|
import Divider from 'primevue/divider'
|
||||||
import InputText from 'primevue/inputtext'
|
import InputText from 'primevue/inputtext'
|
||||||
import MenuBar from 'primevue/menubar'
|
import MenuBar from 'primevue/menubar'
|
||||||
import ProgressBar from 'primevue/progressbar'
|
import ProgressBar from 'primevue/progressbar'
|
||||||
|
@ -34,6 +35,7 @@ app.component('Button', Button)
|
||||||
app.component('Card', Card)
|
app.component('Card', Card)
|
||||||
app.component('Column', Column)
|
app.component('Column', Column)
|
||||||
app.component('DataTable', DataTable)
|
app.component('DataTable', DataTable)
|
||||||
|
app.component('Divider', Divider)
|
||||||
app.component('InputText', InputText)
|
app.component('InputText', InputText)
|
||||||
app.component('MenuBar', MenuBar)
|
app.component('MenuBar', MenuBar)
|
||||||
app.component('ProgressBar', ProgressBar)
|
app.component('ProgressBar', ProgressBar)
|
||||||
|
@ -45,9 +47,7 @@ app.component('FileSelect', FileSelect)
|
||||||
app.component('WireStatistics', WireStatistics)
|
app.component('WireStatistics', WireStatistics)
|
||||||
|
|
||||||
app.provide('trackers', [
|
app.provide('trackers', [
|
||||||
'wss://tracker.btorrent.xyz',
|
|
||||||
'wss://tracker.openwebtorrent.com',
|
'wss://tracker.openwebtorrent.com',
|
||||||
'wss://tracker.fastcast.nz',
|
|
||||||
'wss://tracker.sloppyta.co:443/announce',
|
'wss://tracker.sloppyta.co:443/announce',
|
||||||
'wss://tracker.files.fm:7073/announce',
|
'wss://tracker.files.fm:7073/announce',
|
||||||
'wss://open.tube:443/tracker/socket',
|
'wss://open.tube:443/tracker/socket',
|
||||||
|
@ -63,15 +63,12 @@ app.provide('rtcConfig', {
|
||||||
iceServers: [
|
iceServers: [
|
||||||
{
|
{
|
||||||
urls: [
|
urls: [
|
||||||
'stun:stun.l.google.com:19302',
|
'stun:stun.l.google.com:19302'
|
||||||
'stun:global.stun.twilio.com:3478'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urls: [
|
urls: [
|
||||||
'turn:relay.instant.io:443?transport=udp',
|
'turn:relay.instant.io:443?transport=udp'
|
||||||
'turn:relay.instant.io:443?transport=tcp',
|
|
||||||
'turns:relay.instant.io:443?transport=tcp'
|
|
||||||
],
|
],
|
||||||
username: 'relay.instant.io',
|
username: 'relay.instant.io',
|
||||||
credential: 'nepal-cheddar-baize-oleander'
|
credential: 'nepal-cheddar-baize-oleander'
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<h1>Host</h1>
|
<h1>Host</h1>
|
||||||
<FileSelect v-if="!video.file" message="Drag a H.264 encoded MP4 here to start a screen." :disabled="!!video.file" @selected="onFilesSelected" />
|
<FileSelect v-if="!video" message="Drag a H.264 encoded MP4 here to start a screen." :disabled="!!video" @selected="onFilesSelected" />
|
||||||
<Card v-else-if="!state.active">
|
<Card v-show="video">
|
||||||
|
<template #content>
|
||||||
|
<video ref="player" controls="true"></video>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
<Card v-if="video && !active">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="p-text-center">
|
<div class="p-text-center">
|
||||||
<ProgressSpinner />
|
<ProgressSpinner />
|
||||||
|
@ -9,140 +14,176 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
<div v-show="state.active && !!video.file">
|
<div v-if="video && active">
|
||||||
<Card>
|
|
||||||
<template #content>
|
|
||||||
<video ref="player"></video>
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
<Card>
|
||||||
<template #title>
|
<template #title>
|
||||||
Properties
|
Properties
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="p-grid">
|
<div class="p-grid">
|
||||||
<div class="p-col-12 p-md-4">
|
<div class="p-col-12 p-md-3">
|
||||||
<h5>Filename</h5>
|
<h5>Filename</h5>
|
||||||
<span>{{video.name}}</span>
|
<span>{{stats.name}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-col-12 p-md-4">
|
<div class="p-col-12 p-md-3">
|
||||||
<h5>Duration</h5>
|
|
||||||
<span>{{video.hDuration}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="p-col-12 p-md-4">
|
|
||||||
<h5>File size</h5>
|
<h5>File size</h5>
|
||||||
<span>{{video.hSize}}</span>
|
<span>{{stats.size}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-col-12 p-md-3">
|
||||||
|
<h5>Uploaded</h5>
|
||||||
|
<span>{{stats.uploaded}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-col-12 p-md-3">
|
||||||
|
<h5>Duration</h5>
|
||||||
|
<span>{{stats.duration}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-col-12">
|
<div class="p-col-12">
|
||||||
<h5>ID</h5>
|
<h5>ID</h5>
|
||||||
<div class="p-fluid">
|
<div class="p-fluid">
|
||||||
<InputText class="p-inputtext-sm" :value="state.infoHash" disabled />
|
<InputText class="p-inputtext-sm" :value="stats.infoHash" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
<WireStatistics :wireStatistics="wireStatistics" sortField="uploaded" />
|
<WireStatistics :downloadSpeed="stats.downloadSpeed" :uploadSpeed="stats.uploadSpeed" :wireStatistics="wireStatistics" sortField="uploaded" />
|
||||||
|
<teleport to="#menuBarEnd">
|
||||||
|
<Button class="p-button-secondary" label="Share" icon="pi pi-share-alt" @click="onButtonClick" />
|
||||||
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { inject, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
import { computed, inject, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||||
import { useToast } from 'primevue/usetoast'
|
import { useToast } from 'primevue/usetoast'
|
||||||
import WebTorrent from 'webtorrent/webtorrent.min.js'
|
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import prettyMilliseconds from 'pretty-ms'
|
import prettyMilliseconds from 'pretty-ms'
|
||||||
|
import WebTorrent from 'webtorrent/webtorrent.min.js'
|
||||||
import updateWireStatistics from '@/helpers/wire-statistics'
|
import updateWireStatistics from '@/helpers/wire-statistics'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
columns: [
|
||||||
|
{ field: 'remoteAddress', header: 'Remote address' },
|
||||||
|
{ field: 'uploadSpeed', header: 'Upload speed' },
|
||||||
|
{ field: 'uploaded', header: 'Uploaded' }
|
||||||
|
]
|
||||||
|
},
|
||||||
setup () {
|
setup () {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const trackers = inject('trackers')
|
const trackers = inject('trackers')
|
||||||
const rtcConfig = inject('rtcConfig')
|
const rtcConfig = inject('rtcConfig')
|
||||||
|
|
||||||
|
// State variables.
|
||||||
var webTorrent = null
|
var webTorrent = null
|
||||||
var wireUpdateHandle = null
|
const active = ref(false)
|
||||||
const player = ref(null)
|
const player = ref(null)
|
||||||
const video = reactive({
|
const video = ref(null)
|
||||||
file: null,
|
const wireStatistics = reactive([])
|
||||||
name: '',
|
const state = reactive({
|
||||||
|
torrent: null,
|
||||||
|
uploaded: 0,
|
||||||
|
uploadSpeed: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
hSize: '',
|
wires: computed(() => state.torrent?.wires ?? [])
|
||||||
hDuration: ''
|
|
||||||
})
|
})
|
||||||
const state = reactive({
|
const stats = reactive({
|
||||||
active: false,
|
infoHash: computed(() => state.torrent?.infoHash ?? 'Unknown'),
|
||||||
torrent: null,
|
uploaded: computed(() => prettyBytes(state.uploaded ?? 0)),
|
||||||
infoHash: '',
|
uploadSpeed: computed(() => prettyBytes(state.uploadSpeed ?? 0) + 'ps'),
|
||||||
uploaded: 0,
|
name: computed(() => video.value?.name ?? 'Unknown'),
|
||||||
wires: []
|
size: computed(() => prettyBytes(state.torrent?.length ?? 0)),
|
||||||
|
duration: computed(() => prettyMilliseconds(state.duration ?? 0))
|
||||||
})
|
})
|
||||||
const wireStatistics = reactive([])
|
|
||||||
|
// Events.
|
||||||
|
const statsHandle = setInterval(() => {
|
||||||
|
if (state.torrent) {
|
||||||
|
state.downloaded = state.torrent?.downloaded
|
||||||
|
state.uploaded = state.torrent?.uploaded
|
||||||
|
state.downloadSpeed = state.torrent?.downloadSpeed
|
||||||
|
state.uploadSpeed = state.torrent?.uploadSpeed
|
||||||
|
updateWireStatistics(state.wires, wireStatistics)
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
webTorrent = new WebTorrent({ tracker: { rtcConfig: rtcConfig } })
|
webTorrent = new WebTorrent({ tracker: { rtcConfig: rtcConfig } })
|
||||||
|
webTorrent.on('error', () => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (webTorrent) {
|
if (webTorrent) {
|
||||||
webTorrent.destroy()
|
webTorrent.destroy()
|
||||||
}
|
}
|
||||||
if (wireUpdateHandle) {
|
if (statsHandle) {
|
||||||
clearInterval(wireUpdateHandle)
|
clearInterval(statsHandle)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onFilesSelected = (f) => {
|
const onFilesSelected = (f) => {
|
||||||
if (f?.length !== 1 || !f[0].name.endsWith('.mp4') || f[0].type !== 'video/mp4') {
|
if (f?.length !== 1 ||
|
||||||
toast.add({ severity: 'error', summary: 'Bad file input', detail: 'You must select a single H.264 encoded MP4.', life: 3000 })
|
!f[0].name.endsWith('.mp4') ||
|
||||||
|
f[0].type !== 'video/mp4') {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Bad file input',
|
||||||
|
detail: 'You must select a single H.264 encoded MP4.',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// Load video in DOM.
|
// Load video in DOM.
|
||||||
player.value.src = URL.createObjectURL(f[0])
|
player.value.src = URL.createObjectURL(f[0])
|
||||||
|
|
||||||
// Load video information such that it can be rendered.
|
// Load video information such that it can be rendered.
|
||||||
video.file = f[0]
|
|
||||||
video.name = f[0].name
|
|
||||||
video.size = f[0].size
|
|
||||||
video.hSize = prettyBytes(f[0].size)
|
|
||||||
player.value.addEventListener('loadedmetadata', (e) => {
|
player.value.addEventListener('loadedmetadata', (e) => {
|
||||||
video.duration = e.target.duration
|
video.value = f[0]
|
||||||
video.hDuration = prettyMilliseconds(e.target.duration * 1000)
|
state.duration = (e.target?.duration ?? 0) * 1000
|
||||||
})
|
})
|
||||||
|
|
||||||
// Seed the video.
|
// Seed the video.
|
||||||
seedVideo(f[0])
|
seedFile(f[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seedVideo = (video) => {
|
const onButtonClick = () => {
|
||||||
webTorrent.seed(video, { announce: trackers }, torrent => {
|
alert('Not implemented yet!')
|
||||||
state.active = true
|
}
|
||||||
state.infoHash = torrent.infoHash
|
|
||||||
state.wires = torrent.wires
|
|
||||||
|
|
||||||
torrent.on('upload', bytes => {
|
|
||||||
state.uploaded += bytes
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// Utility functions.
|
||||||
|
const seedFile = (file) => {
|
||||||
|
webTorrent.seed(file, { announce: trackers }, torrent => {
|
||||||
|
torrent.on('error', () => {})
|
||||||
|
torrent.on('warning', () => {})
|
||||||
torrent.on('wire', wire => {
|
torrent.on('wire', wire => {
|
||||||
toast.add({ severity: 'info', summary: 'New watcher', detail: 'Someone has joined your screen.', life: 3000 })
|
toast.add({
|
||||||
|
severity: 'info',
|
||||||
|
summary: 'New watcher',
|
||||||
|
detail: 'Someone has joined your screen.',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
wireUpdateHandle = setInterval(() => {
|
toast.add({
|
||||||
updateWireStatistics(state.wires, wireStatistics)
|
severity: 'success',
|
||||||
window.w = wireStatistics
|
summary: 'Video added',
|
||||||
wireStatistics.set()
|
detail: `You are now sharing ${file.name}`,
|
||||||
}, 250)
|
life: 5000
|
||||||
|
})
|
||||||
|
|
||||||
toast.add({ severity: 'success', summary: 'Video added', detail: `You are now sharing ${video.name}`, life: 5000 })
|
state.torrent = torrent
|
||||||
|
active.value = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onFilesSelected,
|
onFilesSelected,
|
||||||
|
onButtonClick,
|
||||||
|
active,
|
||||||
player,
|
player,
|
||||||
video,
|
video,
|
||||||
state,
|
stats,
|
||||||
wireStatistics
|
wireStatistics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<h1>Watch</h1>
|
<h1>Watch</h1>
|
||||||
<Card class="p-text-center" v-if="!state.active">
|
<Card v-show="active">
|
||||||
|
<template #content>
|
||||||
|
<video ref="player" controls="true"></video>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
<Card class="p-text-center" v-if="!active">
|
||||||
<template #content>
|
<template #content>
|
||||||
<ProgressSpinner />
|
<ProgressSpinner />
|
||||||
<h2>Waiting for connections. Please wait...</h2>
|
<h2>Waiting for connections. Please wait...</h2>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
<div v-show="state.active">
|
<div v-else>
|
||||||
<Card >
|
|
||||||
<template #content>
|
|
||||||
<ProgressBar class="p-mb-2" :value="progress" :showValue="false" />
|
|
||||||
<video ref="player"></video>
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
<Card>
|
||||||
<template #title>
|
<template #title>
|
||||||
Properties
|
Properties
|
||||||
|
@ -21,31 +20,51 @@
|
||||||
<div class="p-grid">
|
<div class="p-grid">
|
||||||
<div class="p-col-12 p-md-4">
|
<div class="p-col-12 p-md-4">
|
||||||
<h5>Filename</h5>
|
<h5>Filename</h5>
|
||||||
<span>{{video.name}}</span>
|
<span>{{stats.name}}</span>
|
||||||
</div>
|
|
||||||
<div class="p-col-12 p-md-4">
|
|
||||||
<h5>Duration</h5>
|
|
||||||
<span>{{video.hDuration}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="p-col-12 p-md-4">
|
<div class="p-col-12 p-md-4">
|
||||||
<h5>File size</h5>
|
<h5>File size</h5>
|
||||||
<span>{{video.hSize}}</span>
|
<span>{{stats.size}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-col-12 p-md-4">
|
||||||
|
<h5>Duration</h5>
|
||||||
|
<span>{{stats.duration}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-col-12 p-md-4">
|
||||||
|
<h5>Downloaded</h5>
|
||||||
|
<span>{{stats.downloaded}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-col-12 p-md-4">
|
||||||
|
<h5>Uploaded</h5>
|
||||||
|
<span>{{stats.uploaded}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-col-12 p-md-4">
|
||||||
|
<h5>Ratio</h5>
|
||||||
|
<span>{{stats.ratio}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-col-12">
|
<div class="p-col-12">
|
||||||
<h5>ID</h5>
|
<h5>ID</h5>
|
||||||
<div class="p-fluid">
|
<div class="p-fluid">
|
||||||
<InputText class="p-inputtext-sm" :value="state.infoHash" disabled />
|
<InputText class="p-inputtext-sm" :value="stats.infoHash" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-col-12">
|
||||||
|
<h5>Progress</h5>
|
||||||
|
<ProgressBar class="p-mb-2" :value="stats.progress" :showValue="false" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
<WireStatistics :wireStatistics="wireStatistics" sortField="downloaded" />
|
<WireStatistics :downloadSpeed="stats.downloadSpeed" :uploadSpeed="stats.uploadSpeed" :wireStatistics="wireStatistics" sortField="downloaded" />
|
||||||
|
<teleport to="#menuBarEnd">
|
||||||
|
<Button class="p-button-secondary" label="Share" icon="pi pi-share-alt" @click="onButtonClick" />
|
||||||
|
<Button class="p-button-secondary p-ml-2" label="Download" icon="pi pi-download" @click="onButtonClick" />
|
||||||
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { inject, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
import { computed, inject, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useToast } from 'primevue/usetoast'
|
import { useToast } from 'primevue/usetoast'
|
||||||
import WebTorrent from 'webtorrent/webtorrent.min.js'
|
import WebTorrent from 'webtorrent/webtorrent.min.js'
|
||||||
|
@ -65,36 +84,62 @@ export default {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const trackers = inject('trackers')
|
const trackers = inject('trackers')
|
||||||
const rtcConfig = inject('rtcConfig')
|
const rtcConfig = inject('rtcConfig')
|
||||||
|
|
||||||
|
// State variables.
|
||||||
var webTorrent = null
|
var webTorrent = null
|
||||||
var wireUpdateHandle = null
|
const active = ref(false)
|
||||||
const player = ref(null)
|
const player = ref(null)
|
||||||
const progress = ref()
|
const video = ref(null)
|
||||||
const video = reactive({
|
const wireStatistics = reactive([])
|
||||||
file: null,
|
const state = reactive({
|
||||||
name: '',
|
torrent: null,
|
||||||
|
downloaded: 0,
|
||||||
|
uploaded: 0,
|
||||||
|
downloadSpeed: 0,
|
||||||
|
uploadSpeed: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
hSize: '',
|
wires: computed(() => state.torrent?.wires ?? [])
|
||||||
hDuration: ''
|
|
||||||
})
|
})
|
||||||
const state = reactive({
|
const stats = reactive({
|
||||||
active: false,
|
infoHash: computed(() => state.torrent?.infoHash ?? 'Unknown'),
|
||||||
torrent: null,
|
downloaded: computed(() => prettyBytes(state.downloaded ?? 0)),
|
||||||
infoHash: '',
|
uploaded: computed(() => prettyBytes(state.uploaded ?? 0)),
|
||||||
downloaded: 0,
|
downloadSpeed: computed(() => prettyBytes(state.downloadSpeed ?? 0) + 'ps'),
|
||||||
wires: []
|
uploadSpeed: computed(() => prettyBytes(state.uploadSpeed ?? 0) + 'ps'),
|
||||||
|
ratio: computed(() => (state.uploaded / state.downloaded ?? 0).toFixed(2)),
|
||||||
|
progress: computed(() => (state.downloaded / video.value?.length ?? 0) * 100),
|
||||||
|
name: computed(() => video.value?.name ?? 'Unknown'),
|
||||||
|
size: computed(() => prettyBytes(state.torrent?.length ?? 0)),
|
||||||
|
duration: computed(() => prettyMilliseconds(state.duration ?? 0))
|
||||||
})
|
})
|
||||||
const wireStatistics = reactive([])
|
|
||||||
|
// Events.
|
||||||
|
const statsHandle = setInterval(() => {
|
||||||
|
if (state.torrent) {
|
||||||
|
state.downloaded = state.torrent?.downloaded
|
||||||
|
state.uploaded = state.torrent?.uploaded
|
||||||
|
state.downloadSpeed = state.torrent?.downloadSpeed
|
||||||
|
state.uploadSpeed = state.torrent?.uploadSpeed
|
||||||
|
updateWireStatistics(state.wires, wireStatistics)
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
if (!/^([a-f0-9]{40})$/.test(props.id)) {
|
if (!/^([a-f0-9]{40})$/.test(props.id)) {
|
||||||
toast.add({ severity: 'error', summary: 'Bad ID', detail: 'Please enter a valid ID.', life: 3000 })
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Bad ID',
|
||||||
|
detail: 'Please enter a valid ID.',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
router.push('/join')
|
router.push('/join')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
webTorrent = new WebTorrent({ tracker: { rtcConfig: rtcConfig } })
|
webTorrent = new WebTorrent({ tracker: { rtcConfig: rtcConfig } })
|
||||||
|
webTorrent.on('error', () => {})
|
||||||
downloadVideo(props.id)
|
downloadVideo(props.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -102,66 +147,58 @@ export default {
|
||||||
if (webTorrent) {
|
if (webTorrent) {
|
||||||
webTorrent.destroy()
|
webTorrent.destroy()
|
||||||
}
|
}
|
||||||
if (wireUpdateHandle) {
|
if (statsHandle) {
|
||||||
clearInterval(wireUpdateHandle)
|
clearInterval(statsHandle)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onButtonClick = () => {
|
||||||
|
alert('Not implemented yet!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility functions.
|
||||||
const downloadVideo = (infoHash) => {
|
const downloadVideo = (infoHash) => {
|
||||||
webTorrent.add(infoHash, { announce: trackers }, torrent => {
|
webTorrent.add(infoHash, { announce: trackers }, torrent => {
|
||||||
|
torrent.on('error', () => {})
|
||||||
|
torrent.on('warning', () => {})
|
||||||
torrent.on('wire', () => {
|
torrent.on('wire', () => {
|
||||||
toast.add({ severity: 'info', summary: 'New watcher', detail: 'Someone has joined your screen.', life: 3000 })
|
toast.add({
|
||||||
|
severity: 'info',
|
||||||
|
summary: 'New watcher',
|
||||||
|
detail: 'Someone has joined your screen.',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
torrent.on('noPeers', () => {
|
torrent.on('noPeers', () => {
|
||||||
toast.add({ severity: 'warning', summary: 'No peers', detail: 'No one is sharing this video at the moment.', life: 3000 })
|
toast.add({
|
||||||
})
|
severity: 'warning',
|
||||||
|
summary: 'No peers',
|
||||||
torrent.on('upload', bytes => {
|
detail: 'No one is sharing this video at the moment.',
|
||||||
})
|
life: 3000
|
||||||
|
|
||||||
torrent.on('download', bytes => {
|
|
||||||
if (!state.active) {
|
|
||||||
state.active = true
|
|
||||||
initialise(torrent)
|
|
||||||
}
|
|
||||||
state.downloaded += bytes
|
|
||||||
progress.value = state.downloaded / video.size * 100
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const initialise = (torrent) => {
|
|
||||||
state.torrent = torrent
|
|
||||||
state.infoHash = torrent.infoHash
|
|
||||||
state.wires = torrent.wires
|
|
||||||
|
|
||||||
var file = torrent.files.find(file => {
|
var file = torrent.files.find(file => {
|
||||||
return file.name.endsWith('.mp4')
|
return file.name.endsWith('.mp4')
|
||||||
})
|
})
|
||||||
|
|
||||||
video.file = file
|
|
||||||
video.name = file.name
|
|
||||||
video.size = file.length
|
|
||||||
video.hSize = prettyBytes(file.length)
|
|
||||||
player.value.addEventListener('loadedmetadata', (e) => {
|
player.value.addEventListener('loadedmetadata', (e) => {
|
||||||
video.duration = e.target.duration
|
video.value = file
|
||||||
video.hDuration = prettyMilliseconds(e.target.duration * 1000)
|
state.duration = (e.target?.duration ?? 0) * 1000
|
||||||
})
|
})
|
||||||
|
|
||||||
file.renderTo(player.value)
|
file.renderTo(player.value)
|
||||||
|
|
||||||
wireUpdateHandle = setInterval(() => {
|
state.torrent = torrent
|
||||||
window.w = wireStatistics
|
active.value = true
|
||||||
updateWireStatistics(state.wires, wireStatistics)
|
})
|
||||||
}, 250)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
onButtonClick,
|
||||||
|
active,
|
||||||
player,
|
player,
|
||||||
video,
|
stats,
|
||||||
state,
|
|
||||||
progress,
|
|
||||||
wireStatistics
|
wireStatistics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue