This is the template project that's checked out and configured when you run the bando-up command from ljsthw-bandolier. This is where the code really lives.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bandolier-template/client/wt.js

206 lines
5.7 KiB

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
const getClient = () => {
// super hacks!
if(LAZY_CLIENT) {
return LAZY_CLIENT;
} else {
// eslint will complain but webtorrent is loaded by <script>
let client = new WebTorrent(webtorrent);
// would need to do some kind of global status for these since it's not per torrent
if(webtorrent.rate !== undefined) {
client.throttleDownload(webtorrent.rate);
client.throttleUpload(webtorrent.rate);
}
client.on('error', err => log.error(err));
client.on('listening', () => log.debug('LISTENING:'));
LAZY_CLIENT = client;
return LAZY_CLIENT;
}
}
export class WTEvents {
constructor(media, video_opts) {
assert(media, "Media must exist.");
this.media = media;
this.video_opts = video_opts || {autoplay: false, controls: true, muted: false}
this.appendId = `append-${media.target_id}`;
}
setTorrent(torrent) {
// TODO: refine this
this.media.torrent = torrent;
this.torrent = torrent;
}
videoReady(file) {
this.media.kind = 'video';
file.renderTo(`#${this.appendId}`, this.video_opts);
file.getBlobURL((err, url) => {
if(err) log.error(err, "getBlobURL error videoReady");
this.download_available(url);
});
}
audioReady(file) {
this.media.kind = 'audio';
file.renderTo(`#${this.appendId}`, this.video_opts);
file.getBlobURL((err, url) => {
if(err) log.error(err, "getBlobURL error audioReady");
this.download_available(url);
});
}
otherReady(file) {
file.renderTo(`#${this.appendId}`, (err, elem) => {
// TODO: hmmm do we need to do anything here with elem?
if(err) {
log.error(err);
} else {
// keep this active for 60 second
let client = getClient();
setTimeout(() => client.remove(this.torrent.infoHash), seedTimeout);
}
});
file.getBlobURL((err, url) => {
if(err) log.error(err, "getBlobURL error");
this.download_available(url);
});
}
download_available(url) {
log.debug("download url available");
}
done() {
log.debug("done", this.media);
}
infoHash(hash) {
this.media.info_hash = hash;
}
metadata(data) {
this.media.metadata = data;
}
ready() {
return true;
}
warning(msg) {
log.debug("WT WARNING", msg);
}
error(msg) {
log.debug("WT ERROR", msg);
}
download(bytes) {
return bytes !== undefined;
}
upload(bytes) {
return bytes !== undefined;
}
wire(wire) {
// this weird idiom is to make eslint shut up for these placeholders
return wire !== undefined;
}
noPeers(announceType) {
log.debug('noPeers from', announceType);
}
client_error(err) {
log.error(err, "client error");
return true;
}
}
const handle_torrent = (media, torrent, events) => {
torrent.files.forEach((file) => {
media.file_name = file.name;
if(media.file_name.endsWith('mp4') || media.file_name.endsWith('webm')) {
events.videoReady(file);
} else if(media.file_name.endsWith('mp3')) {
events.audioReady(file);
} else {
events.otherReady(file);
}
});
// do not remove these wrapping functions. without them you'll have the classic JS error
// of this inside the events class being randomly set to torrent or whatever it wants.
// You have to call them inside a closure like this to make sure events.this doesn't change.
torrent.on('done', () => events.done());
torrent.on('infoHash', hash => events.infoHash(hash));
torrent.on('metadata', data => events.metadata(data));
torrent.on('ready', () => events.ready());
torrent.on('warning', warning => events.warning(warning));
torrent.on('error', msg => events.error(msg));
torrent.on('download', bytes => events.download(bytes));
torrent.on('upload', bytes => events.upload(bytes));
torrent.on('wire', wire => events.wire(wire));
torrent.on('noPeers', announceType => events.noPeers(announceType));
}
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");
media.full_torrent_url = `${base_host}${media.torrent_url}`;
let res = await fetch(media.full_torrent_url, { credentials: "same-origin" });
if(res.status === 200) {
media.torrent_file = await res.blob();
return true;
} else {
return false;
}
}
// 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");
let client = getClient();
client.add(media.torrent_file, {private: webtorrent.private, withCredentials: true}, (torrent) => {
events.setTorrent(torrent);
handle_torrent(media, torrent, events);
});
}
export const remove = (media) => {
if(media.torrent) {
getClient().remove(media.torrent);
} else {
log.error("remove called with a media object without a torrent", media);
}
}
export default { load, remove, fetch_torrent_file, WTEvents };