aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client-web/source/helper.ts2
-rw-r--r--client-web/source/resource/file.ts92
-rw-r--r--client-web/source/user/remote.ts12
-rw-r--r--readme.md2
4 files changed, 90 insertions, 18 deletions
diff --git a/client-web/source/helper.ts b/client-web/source/helper.ts
index d43fc3e..adb5f08 100644
--- a/client-web/source/helper.ts
+++ b/client-web/source/helper.ts
@@ -84,3 +84,5 @@ export function notify(body: string, author?: string) {
else
new Notification(`keks-meet`, { body })
}
+
+export function sleep(delay: number) { return new Promise(r => setTimeout(r, delay)) } \ No newline at end of file
diff --git a/client-web/source/resource/file.ts b/client-web/source/resource/file.ts
index c54abc8..d76961d 100644
--- a/client-web/source/resource/file.ts
+++ b/client-web/source/resource/file.ts
@@ -1,23 +1,32 @@
-import { ebutton, ediv, espan } from "../helper.ts";
+import { ebutton, ediv, espan, sleep } from "../helper.ts";
import { LocalResource, ResourceHandlerDecl } from "./mod.ts";
export const resource_file: ResourceHandlerDecl = {
kind: "file",
new_remote(info, user, enable) {
+ const download_button = ebutton("Download", {
+ onclick: self => {
+ enable()
+ self.textContent = "Downloading…"
+ self.disabled = true
+ }
+ })
return {
info,
el: ediv({},
espan(`File: ${JSON.stringify(info.label)}`),
- ebutton("Download", {
- onclick: self => {
- enable()
- self.disabled = true
- }
- })
+ download_button,
),
on_statechange(_s) { },
- on_enable(channel, _disable) {
+ on_enable(channel, disable) {
if (!(channel instanceof RTCDataChannel)) throw new Error("not a data channel");
+ // TODO stream
+ let position = 0
+ const buffer = new Uint8Array(info.size!)
+
+ const display = transfer_status_el()
+ this.el.appendChild(display.el)
+
channel.onopen = _ev => {
console.log(`${user.display_name}: channel open`);
}
@@ -26,6 +35,25 @@ export const resource_file: ResourceHandlerDecl = {
}
channel.onclose = _ev => {
console.log(`${user.display_name}: channel closed`);
+ const a = document.createElement("a")
+ a.href = URL.createObjectURL(new Blob([buffer], { type: "text/plain" }))
+ a.download = info.label ?? "file"
+ a.click()
+ this.el.removeChild(display.el)
+ download_button.disabled = false
+ download_button.textContent = "Download"
+ disable()
+ }
+ channel.onmessage = ev => {
+ const reader = new FileReader();
+ reader.onload = function (event) {
+ const arr = new Uint8Array(event.target!.result as ArrayBuffer);
+ for (let i = 0; i < arr.length; i++, position++) {
+ buffer[position] = arr[i]
+ }
+ display.status = `${position} / ${info.size}`
+ };
+ reader.readAsArrayBuffer(ev.data);
}
}
}
@@ -57,24 +85,64 @@ function file_res_inner(file: File): LocalResource {
),
on_request(user, create_channel) {
const channel = create_channel()
+ channel.bufferedAmountLowThreshold = 1 << 16 // this appears to be the buffer size in firefox for reading files
const reader = file.stream().getReader()
- console.log(`${user.display_name} started requested file`);
- channel.onbufferedamountlow = async () => {
+
+ console.log(`${user.display_name} started transfer`);
+ const display = transfer_status_el()
+ transfers_el.appendChild(display.el)
+ display.status = "Waiting for data channel to open…"
+ let position = 0
+
+ const finish = async () => {
+ while (channel.bufferedAmount) {
+ display.status = `Draining buffers… (buffer: ${channel.bufferedAmount})`
+ await sleep(10)
+ }
+ return channel.close()
+ }
+ const feed = async () => {
const { value: chunk, done } = await reader.read()
- console.log(chunk, done);
+ if (!chunk) console.warn("no chunk");
+ if (done) return await finish()
+ position += chunk.length
channel.send(chunk)
- if (!done) console.log("transfer done");
+ display.status = `${position} / ${file.size} (buffer: ${channel.bufferedAmount})`
}
+ const feed_until_full = async () => {
+ // this has to do with a bad browser implementation
+ // https://github.com/w3c/webrtc-pc/issues/1979
+ while (channel.bufferedAmount < channel.bufferedAmountLowThreshold * 2 && channel.readyState == "open") {
+ await feed()
+ }
+ }
+ channel.onbufferedamountlow = () => feed_until_full()
channel.onopen = _ev => {
+ display.status = "Buffering…"
console.log(`${user.display_name}: channel open`);
+ feed_until_full()
}
channel.onerror = _ev => {
console.log(`${user.display_name}: channel error`);
}
+ channel.onclosing = _ev => {
+ display.status = "Channel closing…"
+ }
channel.onclose = _ev => {
console.log(`${user.display_name}: channel closed`);
+ transfers_el.removeChild(display.el)
}
return channel
}
}
+}
+
+function transfer_status_el() {
+ const status = espan("…")
+ return {
+ el: ediv({ class: "progress" }, status),
+ set status(s: string) {
+ status.textContent = s
+ }
+ }
} \ No newline at end of file
diff --git a/client-web/source/user/remote.ts b/client-web/source/user/remote.ts
index 2c3fd58..5517dbe 100644
--- a/client-web/source/user/remote.ts
+++ b/client-web/source/user/remote.ts
@@ -61,7 +61,7 @@ export class RemoteUser extends User {
}
this.pc.onnegotiationneeded = () => {
log("webrtc", `negotiation needed: ${this.display_name}`)
- if (this.negotiation_busy && this.pc.signalingState == "stable") return
+ // if (this.pc.signalingState != "stable") return
this.offer()
this.update_stats()
}
@@ -119,8 +119,9 @@ export class RemoteUser extends User {
}
if (message.request_stop) {
const sender = this.senders.get(message.request_stop.id)
- if (!sender) return log({ scope: "*", warn: true }, "somebody requested us to stop transmitting an unknown resource")
- this.pc.removeTrack(sender)
+ if (sender) this.pc.removeTrack(sender)
+ const dc = this.data_channels.get(message.request_stop.id)
+ if (dc) dc.close()
}
}
send_to(message: RelayMessage) {
@@ -168,14 +169,14 @@ export class RemoteUser extends User {
let stuff = "";
stuff += `ice-conn=${this.pc.iceConnectionState}; ice-gathering=${this.pc.iceGatheringState}; ice-trickle=${this.pc.canTrickleIceCandidates}; signaling=${this.pc.signalingState};\n`
stats.forEach(s => {
- console.log("stat", s);
+ // console.log("stat", s);
if (s.type == "candidate-pair" && s.selected) {
//@ts-ignore trust me, this works
if (!stats.get) return console.warn("no RTCStatsReport.get");
//@ts-ignore trust me, this works
const cpstat = stats.get(s.localCandidateId)
if (!cpstat) return console.warn("no stats");
- console.log("cp", cpstat);
+ // console.log("cp", cpstat);
stuff += `via ${cpstat.candidateType}:${cpstat.protocol}:${cpstat.address}\n`
} else if (s.type == "codec") {
stuff += `using ${s.codecType ?? "dec/enc"}:${s.mimeType}(${s.sdpFmtpLine})\n`
@@ -186,5 +187,4 @@ export class RemoteUser extends User {
console.warn(e);
}
}
-
} \ No newline at end of file
diff --git a/readme.md b/readme.md
index 1a0dc80..0c304d0 100644
--- a/readme.md
+++ b/readme.md
@@ -132,6 +132,8 @@ system works as follows:
- Pin js by bookmarking data:text/html loader page
- convert protocol enums to `A | B | C`
- add "contributing" stuff to readme
+- download files in a streaming manner.
+ - workaround using service worker
## Protocol