Update libs for new Webcast version.

This commit is contained in:
Buster Neece 2022-12-31 09:33:39 -06:00
parent 2d4d574513
commit 48014dab6b
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
9 changed files with 235 additions and 5543 deletions

View File

@ -2,7 +2,7 @@
import gulp from 'gulp';
import babel from 'gulp-babel';
import { deleteAsync as del } from 'del';
import {deleteAsync as del} from 'del';
import rev from 'gulp-rev';
import concat from 'gulp-concat';
import uglify from 'gulp-uglify';
@ -60,13 +60,7 @@ const jsFiles = {
files: [
'node_modules/luxon/build/global/luxon.min.js'
]
},
'webcaster': {
base: null,
files: [
'js/webcaster/*.js'
]
},
}
};
const defaultTasks = Object.keys(jsFiles);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,446 +0,0 @@
// Generated by CoffeeScript 1.11.1
(function() {
var AudioContext, Webcast;
Webcast = {
Encoder: {}
};
if (typeof window !== "undefined") {
window.Webcast = Webcast;
}
if (typeof self !== "undefined") {
self.Webcast = Webcast;
}
Webcast.Encoder.Asynchronous = (function() {
function Asynchronous(arg) {
var blob, j, len1, script, scripts;
this.encoder = arg.encoder, scripts = arg.scripts;
this.mime = this.encoder.mime;
this.info = this.encoder.info;
this.channels = this.encoder.channels;
this.pending = [];
this.scripts = [];
for (j = 0, len1 = scripts.length; j < len1; j++) {
script = scripts[j];
this.scripts.push("'" + script + "'");
}
script = "var window;\nimportScripts(" + (this.scripts.join()) + ");\nvar encoder = " + (this.encoder.toString()) + ";\nself.onmessage = function (e) {\n var type = e.data.type;\n var data = e.data.data;\n if (type === \"buffer\") {\n encoder.encode(data, function (encoded) {\n postMessage(encoded);\n });\n return;\n }\n if (type === \"close\") {\n encoder.close(function (buffer) {\n postMessage({close:true, buffer:buffer});\n self.close();\n });\n return;\n }\n};";
blob = new Blob([script], {
type: "text/javascript"
});
this.worker = new Worker(URL.createObjectURL(blob));
this.worker.onmessage = (function(_this) {
return function(arg1) {
var data;
data = arg1.data;
return _this.pending.push(data);
};
})(this);
}
Asynchronous.prototype.toString = function() {
return "(new Webcast.Encoder.Asynchronous({\n encoder: " + (this.encoder.toString()) + ",\n scripts: [" + (this.scripts.join()) + "]\n}))";
};
Asynchronous.prototype.close = function(fn) {
this.worker.onmessage = (function(_this) {
return function(arg) {
var chunk, data, j, k, len, len1, len2, offset, ref, ref1, ret;
data = arg.data;
if (!data.close) {
_this.pending.push(data);
return;
}
_this.pending.push(data.buffer);
len = 0;
ref = _this.pending;
for (j = 0, len1 = ref.length; j < len1; j++) {
chunk = ref[j];
len += chunk.length;
}
ret = new Uint8Array(len);
offset = 0;
ref1 = _this.pending;
for (k = 0, len2 = ref1.length; k < len2; k++) {
chunk = ref1[k];
ret.set(chunk, offset);
offset += chunk.length;
}
return fn(ret);
};
})(this);
return this.worker.postMessage({
type: "close"
});
};
Asynchronous.prototype.encode = function(buffer, fn) {
this.worker.postMessage({
type: "buffer",
data: buffer
});
return fn(this.pending.shift());
};
return Asynchronous;
})();
if (typeof window !== "undefined") {
AudioContext = window.AudioContext || window.webkitAudioContext;
AudioContext.prototype.createWebcastSource = function(bufferSize, channels, passThrough) {
var context, node, options;
context = this;
node = context.createScriptProcessor(bufferSize, channels, channels);
passThrough || (passThrough = false);
options = {
recorderSource: null,
encoder: null,
socket: null,
passThrough: passThrough || false
};
node.onaudioprocess = function(buf) {
var audio, channel, channelData, j, ref, ref1;
audio = [];
for (channel = j = 0, ref = buf.inputBuffer.numberOfChannels - 1; 0 <= ref ? j <= ref : j >= ref; channel = 0 <= ref ? ++j : --j) {
channelData = buf.inputBuffer.getChannelData(channel);
audio[channel] = channelData;
if (options.passThrough) {
buf.outputBuffer.getChannelData(channel).set(channelData);
} else {
buf.outputBuffer.getChannelData(channel).set(new Float32Array(channelData.length));
}
}
return (ref1 = options.encoder) != null ? typeof ref1.encode === "function" ? ref1.encode(audio, function(data) {
var ref2;
if (data != null) {
return (ref2 = options.socket) != null ? ref2.sendData(data) : void 0;
}
}) : void 0 : void 0;
};
node.setPassThrough = function(b) {
return options.passThrough = b;
};
node.connectSocket = function(encoder, url) {
if (encoder instanceof Webcast.Recorder) {
options.recorderSource = context.createMediaStreamDestination();
node.connect(options.recorderSource);
encoder.start(options.recoderSource.stream, function(data) {
var ref;
if (data != null) {
return (ref = options.socket) != null ? ref.sendData(data) : void 0;
}
});
}
options.encoder = encoder;
return options.socket = new Webcast.Socket({
url: url,
mime: options.encoder.mime,
info: options.encoder.info
});
};
node.close = function(cb) {
var fn, ref, ref1;
if ((ref = options.recorderSource) != null) {
ref.disconnect();
}
options.recorderSource = null;
fn = function() {
var ref1;
if ((ref1 = options.socket) != null) {
ref1.close();
}
options.socket = options.encoder = null;
return typeof cb === "function" ? cb() : void 0;
};
if (((ref1 = options.encoder) != null ? ref1.close : void 0) == null) {
return fn();
}
return options.encoder.close(function(data) {
var ref2;
if ((ref2 = options.socket) != null) {
ref2.sendData(data);
}
return fn();
});
};
node.getSocket = function() {
return options.socket;
};
node.sendMetadata = (function(_this) {
return function(metadata) {
var ref;
return (ref = options.socket) != null ? ref.sendMetadata(metadata) : void 0;
};
})(this);
node.isOpen = function() {
return options != null ? options.socket.isOpen() : void 0;
};
return node;
};
}
Webcast.Encoder.Mp3 = (function() {
Mp3.prototype.mime = "audio/mpeg";
function Mp3(arg) {
this.samplerate = arg.samplerate, this.bitrate = arg.bitrate, this.channels = arg.channels;
this.shine = new Shine({
samplerate: this.samplerate,
bitrate: this.bitrate,
channels: this.channels,
mode: this.channels === 1 ? Shine.MONO : Shine.JOINT_STEREO
});
this.info = {
audio: {
channels: this.channels,
samplerate: this.samplerate,
bitrate: this.bitrate,
encoder: "libshine"
}
};
this;
}
Mp3.prototype.toString = function() {
return "(new Webcast.Encoder.Mp3({\n bitrate: " + this.bitrate + ",\n channels: " + this.channels + ",\n samplerate: " + this.samplerate + "\n }))";
};
Mp3.prototype.close = function(data, fn) {
var flushed, rem;
rem = new Uint8Array;
if (fn != null) {
if ((data != null ? data.length : void 0) > 0) {
rem = this.shine.encode(data);
}
} else {
fn = data;
}
flushed = this.shine.close();
data = new Uint8Array(rem.length + flushed.length);
data.set(rem);
data.set(flushed, rem.length);
return fn(data);
};
Mp3.prototype.encode = function(data, fn) {
data = data.slice(0, this.channels);
return fn(this.shine.encode(data));
};
return Mp3;
})();
Webcast.Encoder.Raw = (function() {
function Raw(arg) {
this.channels = arg.channels, this.samplerate = arg.samplerate;
this.mime = "audio/x-raw,format=S8,channels=" + this.channels + ",layout=interleaved,rate=" + this.samplerate;
this.info = {
audio: {
channels: this.channels,
samplerate: this.samplerate,
encoder: "RAW u8 encoder"
}
};
}
Raw.prototype.toString = function() {
return "(new Webcast.Encoder.Raw({\n channels: " + this.channels + ",\n samplerate: " + this.samplerate + "\n }))";
};
Raw.prototype.doEncode = function(data) {
var buf, chan, channels, i, j, k, ref, ref1, samples;
channels = data.length;
samples = data[0].length;
buf = new Int8Array(channels * samples);
for (chan = j = 0, ref = channels - 1; 0 <= ref ? j <= ref : j >= ref; chan = 0 <= ref ? ++j : --j) {
for (i = k = 0, ref1 = samples - 1; 0 <= ref1 ? k <= ref1 : k >= ref1; i = 0 <= ref1 ? ++k : --k) {
buf[channels * i + chan] = data[chan][i] * 127;
}
}
return buf;
};
Raw.prototype.close = function(data, fn) {
var ret;
ret = new Uint8Array;
if (fn != null) {
if ((data != null ? data.count : void 0) > 0) {
ret = this.doEncode(data);
}
} else {
fn = data;
}
return fn(ret);
};
Raw.prototype.encode = function(data, fn) {
return fn(this.doEncode(data));
};
return Raw;
})();
Webcast.Recorder = (function() {
Recorder.prototype.mime = "audio/ogg";
function Recorder(arg) {
this.samplerate = arg.samplerate, this.bitrate = arg.bitrate, this.channels = arg.channels;
this.info = {
audio: {
channels: this.channels,
samplerate: this.samplerate,
bitrate: this.bitrate,
encoder: "MediaRecorder"
}
};
}
Recorder.prototype.start = function(stream, cb) {
var recorder;
recorder = new MediaRecorder(stream);
return recorder.ondataavailable = (function(_this) {
return function(e) {
var blob;
if (recorder.state === "recording") {
blob = new Blob([e.data], _this.mime);
return cb(blob);
}
};
})(this);
};
return Recorder;
})();
Webcast.Encoder.Resample = (function() {
function Resample(arg) {
var i, j, ref;
this.encoder = arg.encoder, this.samplerate = arg.samplerate, this.type = arg.type;
this.mime = this.encoder.mime;
this.info = this.encoder.info;
this.channels = this.encoder.channels;
this.ratio = parseFloat(this.encoder.samplerate) / parseFloat(this.samplerate);
this.type = this.type || Samplerate.FASTEST;
this.resamplers = [];
this.remaining = [];
for (i = j = 0, ref = this.channels - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {
this.resamplers[i] = new Samplerate({
type: this.type
});
this.remaining[i] = new Float32Array;
}
}
Resample.prototype.toString = function() {
return "(new Webcast.Encoder.Resample({\n encoder: " + (this.encoder.toString()) + ",\n samplerate: " + this.samplerate + ",\n type: " + this.type + "\n }))";
};
Resample.prototype.close = function(fn) {
var data, i, j, ref;
for (i = j = 0, ref = this.remaining.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {
data = this.resamplers[i].process({
data: this.remaining[i],
ratio: this.ratio,
last: true
}).data;
}
return this.encoder.close(data, fn);
};
Resample.prototype.concat = function(a, b) {
var ret;
if (typeof b === "undefined") {
return a;
}
ret = new Float32Array(a.length + b.length);
ret.set(a);
ret.subarray(a.length).set(b);
return ret;
};
Resample.prototype.encode = function(buffer, fn) {
var data, i, j, ref, ref1, used;
for (i = j = 0, ref = this.channels - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {
buffer[i] = this.concat(this.remaining[i], buffer[i]);
ref1 = this.resamplers[i].process({
data: buffer[i],
ratio: this.ratio
}), data = ref1.data, used = ref1.used;
this.remaining[i] = buffer[i].subarray(used);
buffer[i] = data;
}
return this.encoder.encode(buffer, fn);
};
return Resample;
})();
Webcast.Socket = function(arg) {
var hello, info, key, mime, parser, password, send, socket, url, user, value;
url = arg.url, mime = arg.mime, info = arg.info;
parser = document.createElement("a");
parser.href = url;
user = parser.username;
password = parser.password;
parser.username = parser.password = "";
url = parser.href;
socket = new WebSocket(url, "webcast");
socket.mime = mime;
socket.info = info;
hello = {
mime: mime
};
if ((user != null) && user !== "") {
hello.user = socket.user = user;
}
if ((password != null) && password !== "") {
hello.password = socket.password = password;
}
for (key in info) {
value = info[key];
hello[key] = value;
}
send = socket.send;
socket.send = null;
socket.addEventListener("open", function() {
return send.call(socket, JSON.stringify({
type: "hello",
data: hello
}));
});
socket.sendData = function(data) {
if (!socket.isOpen()) {
return;
}
if (!((data != null ? data.length : void 0) > 0)) {
return;
}
if (!(data instanceof ArrayBuffer)) {
data = data.buffer.slice(data.byteOffset, data.length * data.BYTES_PER_ELEMENT);
}
return send.call(socket, data);
};
socket.sendMetadata = function(metadata) {
if (!socket.isOpen()) {
return;
}
return send.call(socket, JSON.stringify({
type: "metadata",
data: metadata
}));
};
socket.isOpen = function() {
return socket.readyState === WebSocket.OPEN;
};
return socket;
};
}).call(this);

View File

@ -0,0 +1,148 @@
import {ref} from "vue";
import Webcast from "~/vendor/webcast/webcast";
export function useWebDjNode() {
const doPlayThrough = ref(false);
const isStreaming = ref(false);
const context = new AudioContext({
sampleRate: 44100
});
const sink = context.createScriptProcessor(256, 2, 2);
sink.onaudioprocess((buf) => {
let channel;
let channelData = buf.inputBuffer.getChannelData(channel);
for (channel = 0; channel < buf.inputBuffer.numberOfChannels - 1; channel++) {
buf.outputBuffer.getChannelData(channel).set(channelData);
}
});
const playThrough = context.createScriptProcessor(256, 2, 2);
playThrough.onaudioprocess((buf) => {
let channel;
let channelData = buf.inputBuffer.getChannelData(channel);
for (channel = 0; channel < buf.inputBuffer.numberOfChannels - 1; channel++) {
if (doPlayThrough.value) {
buf.outputBuffer.getChannelData(channel).set(channelData);
} else {
buf.outputBuffer.getChannelData(channel).set(new Float32Array(channelData.length));
}
}
});
sink.connect(playThrough);
playThrough.connect(context.destination);
const streamNode = context.createMediaStreamDestination();
streamNode.channelCount = 2;
sink.connect(streamNode);
let socket;
let mediaRecorder;
const startStream = (url) => {
isStreaming.value = true;
context.resume();
mediaRecorder = new MediaRecorder(
streamNode.stream,
{
mimeType: "audio/webm;codecs=opus",
audioBitsPerSecond: 128
}
);
socket = new Webcast.Socket(
mediaRecorder,
{
url: url
}
);
mediaRecorder.start(1000);
}
const stopStream = () => {
mediaRecorder?.stop();
isStreaming.value = false;
};
const createAudioSource = ({file, audio}, model, cb) => {
const el = new Audio(URL.createObjectURL(file));
el.controls = false;
el.autoplay = false;
el.loop = false;
let source = null;
el.addEventListener("canplay", () => {
if (source) {
return;
}
source = context.createMediaElementSource(el);
source.play = () => el.play()
source.position = () => el.currentTime;
source.duration = () => el.duration;
source.paused = () => el.paused;
source.stop = () => {
el.pause();
return el.remove();
};
source.pause = () => el.pause;
source.seek = (percent) => {
let time = percent * parseFloat(audio.length);
el.currentTime = time;
return time;
};
return cb(source);
});
};
const createFileSource = (file, model, cb) => {
source?.disconnect();
return createAudioSource(file, model, cb);
};
const createMicrophoneSource = (constraints, cb) => {
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
let source = context.createMediaStreamSource(stream);
source.stop = () => {
let ref = stream.getAudioTracks();
return (ref !== null)
? ref[0].stop()
: 0;
}
return cb(source);
});
};
const sendMetadata = (data) => {
socket?.sendMetadata(data);
};
return {
context,
sink,
doPlayThrough,
playThrough,
streamNode,
startStream,
stopStream,
createAudioSource,
createFileSource,
createMicrophoneSource,
sendMetadata
};
}

View File

@ -0,0 +1,21 @@
export declare const version = "1.0.1";
export declare class Socket {
socket: WebSocket;
constructor({ mediaRecorder, url: rawUrl, info, onError, onOpen, }: {
mediaRecorder: MediaRecorder;
url: string;
info: Record<string, unknown>;
onError?: (_: Event) => void;
onOpen?: (_: Event) => void;
});
isConnected(): boolean;
sendMetadata(data: Record<string, unknown>): void;
}
declare global {
interface Window {
Webcast: {
Socket: typeof Socket;
version: string;
};
}
}

62
frontend/vue/vendor/webcast/webcast.js vendored Normal file
View File

@ -0,0 +1,62 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Socket = exports.version = void 0;
exports.version = "1.0.1";
class Socket {
constructor({ mediaRecorder, url: rawUrl, info, onError, onOpen, }) {
const parser = document.createElement("a");
parser.href = rawUrl;
const user = parser.username;
const password = parser.password;
parser.username = parser.password = "";
const url = parser.href;
this.socket = new WebSocket(url, "webcast");
if (onError)
this.socket.onerror = onError;
const hello = Object.assign(Object.assign(Object.assign({ mime: mediaRecorder.mimeType }, (user ? { user } : {})), (password ? { password } : {})), info);
this.socket.onopen = function onopen(event) {
if (onOpen)
onOpen(event);
this.send(JSON.stringify({
type: "hello",
data: hello,
}));
};
mediaRecorder.ondataavailable = (e) => __awaiter(this, void 0, void 0, function* () {
const data = yield e.data.arrayBuffer();
if (this.isConnected()) {
this.socket.send(data);
}
});
mediaRecorder.onstop = (e) => {
if (this.isConnected()) {
this.socket.close();
}
};
}
isConnected() {
return this.socket.readyState === WebSocket.OPEN;
}
sendMetadata(data) {
this.socket.send(JSON.stringify({
type: "metadata",
data,
}));
}
}
exports.Socket = Socket;
if (typeof window !== "undefined") {
window.Webcast = {
version: "1.0.0",
Socket,
};
}

View File

@ -1,6 +1,8 @@
<?php
/**
* @var App\View\GlobalSections $sections
* @var App\Entity\Station $station
* @var string $wss_url
*/
$this->layout(
@ -13,27 +15,6 @@ $this->layout(
]
);
$jsLibs = [
$this->assetUrl('dist/lib/webcaster/libshine.js'),
$this->assetUrl('dist/lib/webcaster/libsamplerate.js'),
$this->assetUrl('dist/lib/webcaster/taglib.js'),
$this->assetUrl('dist/lib/webcaster/webcast.js'),
];
$libUrls = [];
foreach ($jsLibs as $script) {
$libUrls[] = (string)($router->getBaseUrl()->withPath($script));
}
$scriptLines = [];
foreach ($jsLibs as $jsLib) {
$scriptLines[] = <<<HTML
<script src="{$jsLib}"></script>
HTML;
}
$sections->append('bodyjs', implode("\n", $scriptLines));
echo $this->fetch(
'partials/vue_body',
[
@ -41,7 +22,6 @@ echo $this->fetch(
'id' => 'web_dj',
'props' => [
'stationName' => $station->getName(),
'libUrls' => $libUrls,
'baseUri' => $wss_url,
],
]