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.
205 lines
5.7 KiB
205 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 };
|
|
|