-
Notifications
You must be signed in to change notification settings - Fork 39
Open
Description
Want to share a browser version that uses the fetch readable stream api and MSE.
it uses a fraction of what a browser bundle of what this package uses... And it has no dependencies
Code
{
const mozChunked = 'moz-chunked-arraybuffer';
const supportChunked = (b => {
try {
return (b.responseType = mozChunked), b.responseType == mozChunked
} catch (c) {
return !1
}
})(new XMLHttpRequest);
const supportsFetch = typeof Response !== 'undefined' &&
Response.prototype.hasOwnProperty('body')
const trim = (arr, b = arr.length) => {
for (;~--b && arr[b] === 0;);
return arr.slice(0, b+1)
}
const parseMeta = str => {
const pieces = str.split(';')
const rtn = {}
for (let peace of pieces) {
peace = peace.trim();
if (peace.length > 0) {
const delimiter = /\=(['"])/.exec(peace)
const name = peace.substring(0, delimiter.index)
const value = peace.substring(delimiter.index + 2, peace.length - 1)
rtn[name.trim()] = value.trim()
}
}
return rtn
}
const attach = (req, player) => {
if (!(req instanceof Request))
req = new Request(req)
req.headers.set('icy-metadata', '1')
const decoder = new TextDecoder
const ms = new MediaSource()
const track = player.addTextTrack('metadata')
const {cues} = track
const segments = []
/**********************
0 = reading raw data
1 = reading byteRange
2 = reading meta data
**********************/
let state = 0
let metaint;
let byteLeftToRead = 0;
let sourceBuffer;
player.src = URL.createObjectURL(ms)
player.controls = true
const doAppend = () => {
if (sourceBuffer.updating || !segments.length)
return
let chunk = segments.shift()
// large buffer needs to be sliced so
// metadata is not feed into the sourceBuffer
if (chunk.length > byteLeftToRead) {
const b = chunk.slice(0, byteLeftToRead)
segments.unshift(chunk.slice(b.length))
chunk = b
}
if (state === 2) {
// small buffer needs to fill the remaining metadata
if (chunk.length < byteLeftToRead) {
if (segments.length) {
const a = chunk
const b = segments.shift()
const c = new Int8Array(a.length + b.length)
c.set(a)
c.set(b, a.length)
segments.unshift(c)
}
return
}
}
byteLeftToRead -= chunk.length
if (state === 0) {
// Reading raw metaData
if (byteLeftToRead === 0) {
state = 1
byteLeftToRead = 1
}
sourceBuffer.appendBuffer(chunk)
} else if (state === 1) {
byteLeftToRead = chunk[0] * 16
if (byteLeftToRead > 0) {
state = 2
} else {
state = 0
byteLeftToRead = metaint
}
} else {
chunk = trim(chunk)
const {StreamTitle} = parseMeta(decoder.decode(chunk))
const end = Number.MAX_SAFE_INTEGER
let start = 0
if (cues.length !== 0) {
const last = cues[cues.length - 1]
start = last.endTime = sourceBuffer.buffered.end(0)
}
track.addCue(new VTTCue(start, end, StreamTitle))
state = 0
byteLeftToRead = metaint
}
}
req = supportsFetch ? fetch(req).then(res => {
// Use fetch streaming technique
const reader = res.body.getReader()
const pump = () => reader.read().then(chunk => {
window.e = new Blob([window.e || '', chunk.value], {type: 'application/wergfwe'})
if (chunk.value) segments.push(chunk.value)
doAppend()
return chunk.done ? undefined : pump()
})
byteLeftToRead = metaint = ~~res.headers.get('icy-metaint') || 8192
return pump
}) : supportChunked ? new Promise(rs => {
// Use Firefox XHR's moz-chunked-arraybuffer streaming technique
const xhr = new XMLHttpRequest
xhr.open('get', req.url)
xhr.setRequestHeader('icy-metadata', '1')
xhr.responseType = mozChunked
xhr.onreadystatechange = () => {
if (xhr.readyState === xhr.HEADERS_RECEIVED) {
byteLeftToRead = metaint = ~~xhr.getResponseHeader('icy-metaint') || 8192
}
rs(() => {})
}
xhr.onprogress = () => {
const bytes = new Uint8Array(xhr.response).slice()
segments.push(bytes)
}
xhr.send()
}) : Promise.reject(new Error('unable to stream audio'))
ms.addEventListener('sourceopen', async evt => {
sourceBuffer = evt.target.addSourceBuffer('audio/aac')
sourceBuffer.mode = 'sequence'
req.then(pump => pump())
window.sb = sourceBuffer
sourceBuffer.addEventListener('updateend', () => {
if (!sourceBuffer.updating) ms.duration = sourceBuffer.buffered.end(0)
doAppend()
}, false)
player.play()
}, false);
}
const play = req => ({
in(selector) {
let player;
// turn string into selector
if (typeof selector === 'string') {
selector = document.querySelector(selector)
}
const isNode = selector instanceof HTMLElement
const isMedia = selector instanceof HTMLAudioElement ||
selector instanceof HTMLVideoElement
if (isNode) {
if (isMedia) {
player = selector
} else {
player = new Audio
player.controls = true
selector.appendChild(player)
}
} else {
player = new Audio
}
attach(req, player)
return player
}
})
window.play = play
}
Api
play(url).in(querySelector) // string or node elm (if it's not a video/audio element it will append a audio elm)
play(url).in(window) // plays without appending to body
const player = play(url).in('body') // append a new audio elm to body with controls set to true
// From here just use the media api.
// this will give you the current title of the auido that are being played
player.textTracks[0].oncuechange = evt => console.log(evt.target.activeCues[0].text)
update: included streaming for firefox alos, but it don't support the codec that i pass in addSourceBuffer... solution?
Metadata
Metadata
Assignees
Labels
No labels