|
|
|
@ -1,18 +1,28 @@ |
|
|
|
|
/* |
|
|
|
|
This needs an entire rewrite using the FSM code. Most of this is hacked on |
|
|
|
|
garbage that is unreliable at the best of times. The documentation here is |
|
|
|
|
mostly give what I have a bit of help, and I keep this _mostly_ as an example |
|
|
|
|
of how to work with an older API. In the future I want to rewrite this--or write |
|
|
|
|
an alternative--that uses the `client/fsm.js` library similar to how `HLSVideo.svelte` |
|
|
|
|
works. |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
const seedTimeout = 60000; |
|
|
|
|
import { webtorrent, base_host } from '$/client/config.js'; |
|
|
|
|
import assert from "$/client/assert.js"; |
|
|
|
|
import { log } from "$/client/logging.js"; |
|
|
|
|
|
|
|
|
|
/* This needs an entire rewrite using the FSM code. Most of this is hacked on |
|
|
|
|
* garbage that is unreliable at the best of times. |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
// WARNING: having svelte build the webtorrent client into the script makes it slow to load and massive.
|
|
|
|
|
// it's much faster to set it as a script variable in the index.html but that might cause loading problems
|
|
|
|
|
// WebTorrent then comes from the public/index.html file, not from an import in here
|
|
|
|
|
|
|
|
|
|
let LAZY_CLIENT; // this is lazy loaded by getClient
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
Load the massive WebTorrent client javascript. |
|
|
|
|
|
|
|
|
|
___WARNING___: having svelte build the webtorrent client into the script |
|
|
|
|
makes it slow to load and massive. it's much faster to set it as a script |
|
|
|
|
variable in the index.html but that might cause loading problems WebTorrent |
|
|
|
|
then comes from the public/index.html file, not from an import in here |
|
|
|
|
*/ |
|
|
|
|
const getClient = () => { |
|
|
|
|
// super hacks!
|
|
|
|
|
if(LAZY_CLIENT) { |
|
|
|
@ -34,7 +44,20 @@ const getClient = () => { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
Base class for handling WebTorrent events. Look at the `VideoEvents` class |
|
|
|
|
in `client/components/WTVideo.svelte` for how to use it. You subclass this |
|
|
|
|
if you want to give visual feedback to different events in WebTorrent, such |
|
|
|
|
as new clients connecting, download rates, etc. |
|
|
|
|
*/ |
|
|
|
|
export class WTEvents { |
|
|
|
|
/* |
|
|
|
|
Takes a `Media` object and video player options, then sets up the |
|
|
|
|
necessary things to make a video play after WebTorrent has downloaded it. |
|
|
|
|
|
|
|
|
|
+ `media Media` -- Media object from the database. |
|
|
|
|
+ `video_opts Object` -- Default is `{autoplay: false, controls: true, muted: false}`. |
|
|
|
|
*/ |
|
|
|
|
constructor(media, video_opts) { |
|
|
|
|
assert(media, "Media must exist."); |
|
|
|
|
this.media = media; |
|
|
|
@ -42,12 +65,17 @@ export class WTEvents { |
|
|
|
|
this.appendId = `append-${media.target_id}`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Set the torrent to use. */ |
|
|
|
|
setTorrent(torrent) { |
|
|
|
|
// TODO: refine this
|
|
|
|
|
this.media.torrent = torrent; |
|
|
|
|
this.torrent = torrent; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
Does the work needed when a video is ready. |
|
|
|
|
Be sure to call it with super if you override. |
|
|
|
|
*/ |
|
|
|
|
videoReady(file) { |
|
|
|
|
this.media.kind = 'video'; |
|
|
|
|
file.renderTo(`#${this.appendId}`, this.video_opts); |
|
|
|
@ -57,6 +85,10 @@ export class WTEvents { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
Same as videoReady. This will setup HTML5 `<audio>` tags instead |
|
|
|
|
of `<video>` tags. |
|
|
|
|
*/ |
|
|
|
|
audioReady(file) { |
|
|
|
|
this.media.kind = 'audio'; |
|
|
|
|
file.renderTo(`#${this.appendId}`, this.video_opts); |
|
|
|
@ -66,6 +98,12 @@ export class WTEvents { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
Other kinds of media that WebTorrent will download. This doesn't |
|
|
|
|
reall do much other than call `this.download_available()`. You |
|
|
|
|
probably just want to override `download_available()` then |
|
|
|
|
use that to make a download link. |
|
|
|
|
*/ |
|
|
|
|
otherReady(file) { |
|
|
|
|
file.renderTo(`#${this.appendId}`, (err, elem) => { |
|
|
|
|
// TODO: hmmm do we need to do anything here with elem?
|
|
|
|
@ -84,51 +122,67 @@ export class WTEvents { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Override this to do something when a download of the media is available. */ |
|
|
|
|
download_available(url) { |
|
|
|
|
log.debug("download url available"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Override this to handle when it's done downloading. */ |
|
|
|
|
done() { |
|
|
|
|
log.debug("done", this.media); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Probably don't need to alter this. Use super if you override it. */ |
|
|
|
|
infoHash(hash) { |
|
|
|
|
this.media.info_hash = hash; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Probably don't need to alter this. Use super if you override it. */ |
|
|
|
|
metadata(data) { |
|
|
|
|
this.media.metadata = data; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
Just returns true to WebTorrent, but you can override this if you |
|
|
|
|
want to do something before WebTorrent starts. Your version |
|
|
|
|
must return true. |
|
|
|
|
*/ |
|
|
|
|
ready() { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Logs WebTorrent warnings. */ |
|
|
|
|
warning(msg) { |
|
|
|
|
log.debug("WT WARNING", msg); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Logs WebTorrent errors. */ |
|
|
|
|
error(msg) { |
|
|
|
|
log.debug("WT ERROR", msg); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Event when bytes are downloaded. Override to process it. */ |
|
|
|
|
download(bytes) { |
|
|
|
|
return bytes !== undefined; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Event when bytes are uploaded. Override to process it. */ |
|
|
|
|
upload(bytes) { |
|
|
|
|
return bytes !== undefined; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Notification when there's a new connection to ... a wire. */ |
|
|
|
|
wire(wire) { |
|
|
|
|
// this weird idiom is to make eslint shut up for these placeholders
|
|
|
|
|
return wire !== undefined; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* WebTorrent event for ... uh... noPeers? */ |
|
|
|
|
noPeers(announceType) { |
|
|
|
|
log.debug('noPeers from', announceType); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Handles any client errors, currently just logs. */ |
|
|
|
|
client_error(err) { |
|
|
|
|
log.error(err, "client error"); |
|
|
|
|
return true; |
|
|
|
@ -136,6 +190,10 @@ export class WTEvents { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
This is called inside WebTorrent to process a new torrent being added to |
|
|
|
|
the client. You don't really call this. |
|
|
|
|
*/ |
|
|
|
|
const handle_torrent = (media, torrent, events) => { |
|
|
|
|
torrent.files.forEach((file) => { |
|
|
|
|
media.file_name = file.name; |
|
|
|
@ -164,6 +222,12 @@ const handle_torrent = (media, torrent, events) => { |
|
|
|
|
torrent.on('noPeers', announceType => events.noPeers(announceType)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
Used in `client/components/WTVideo.svelte` to fetch the correct .torrent file |
|
|
|
|
for the media object. |
|
|
|
|
|
|
|
|
|
+ `media Media` -- This is a `lib/models.js:Media` object. |
|
|
|
|
*/ |
|
|
|
|
export const fetch_torrent_file = async (media) => { |
|
|
|
|
assert(media, "media can't be undefined"); |
|
|
|
|
assert(media.torrent_url, "media does not have torrent_url set"); |
|
|
|
@ -180,8 +244,11 @@ export const fetch_torrent_file = async (media) => { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// this thing's job is to load the torrents with webtorrent and then fill out the active_media
|
|
|
|
|
// Debug in the browser with: localStorage.debug = '*';
|
|
|
|
|
/* |
|
|
|
|
This thing's job is to load the torrents with webtorrent and then fill out the active_media. |
|
|
|
|
|
|
|
|
|
Debug in the browser with: localStorage.debug = '*'; |
|
|
|
|
*/ |
|
|
|
|
export const load = (media, events) => { |
|
|
|
|
assert(media, "media can't be undefined"); |
|
|
|
|
assert(media.torrent_file, "media does not have torrent_file set"); |
|
|
|
@ -194,6 +261,15 @@ export const load = (media, events) => { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
WebTorrent does a weird internal client management thing that really |
|
|
|
|
is not friendly with an SPA. To manage it, I have a single WebTorrent |
|
|
|
|
and then you can add or remove media to it. This is how you remove |
|
|
|
|
any media that's active. Use it when people transition off of a page |
|
|
|
|
showing a video so that it stops eating RAM. |
|
|
|
|
|
|
|
|
|
+ `media Media` -- Media object from the database. |
|
|
|
|
*/ |
|
|
|
|
export const remove = (media) => { |
|
|
|
|
if(media.torrent) { |
|
|
|
|
getClient().remove(media.torrent); |
|
|
|
|