src/audio/audioPane.js
/* Single audio play Pane
**
*/
const UI = require('solid-ui')
const $rdf = require('rdflib')
const ns = UI.ns
module.exports = {
icon: UI.icons.iconBase + 'noun_534313.svg',
name: 'audio',
// Does the subject deserve an audio play pane?
label: function (subject, context) {
var kb = context.session.store
var typeURIs = kb.findTypeURIs(subject)
var prefix = $rdf.Util.mediaTypeClass('audio/*').uri.split('*')[0]
for (var t in typeURIs) {
if (t.startsWith(prefix)) return 'Play audio'
}
return null
},
render: function (subject, context) {
const kb = context.session.store
const dom = context.dom
var options = {
autoplay: false,
chain: true,
chainAlbums: true,
loop: false
}
var removeExtension = function (str) {
var dot = str.lastIndexOf('.')
if (dot < 0) return str // if any
var slash = str.lastIndexOf('/')
if (dot < slash) return str
return str.slice(0, dot)
}
// True if there is another file like song.mp3 when this is "song 1.mp3"
// or this is song.m4a
//
var looksRedundant = function (x) {
var folder = kb.any(undefined, ns.ldp('contains'), x)
if (!folder) return false
var contents = kb.each(folder, ns.ldp('contains'))
if (contents.length < 2) return false
var thisName = x.uri
for (var k = 0; k < contents.length; k++) {
var otherName = contents[k].uri
if (
thisName.length > otherName.length &&
thisName.startsWith(removeExtension(otherName))
) {
return true
}
if (
thisName.endsWith('.m4a') &&
otherName.endsWith('.mp3') &&
removeExtension(thisName) === removeExtension(otherName)
) {
return true
}
}
return false
}
// Alternative methods could include:
// Accesing metadata in the audio contol, or paring the audio file
var guessNames = function (x) {
var a = x.uri.split('/').slice(-3) // Hope artist, album, track
var decode = function (str) {
try {
return decodeURIComponent(str)
} catch (e) {
return str
}
}
artistRow.textContent = decode(a[0])
albumRow.textContent = decode(a[1])
trackRow.textContent = decode(removeExtension(a[2]))
}
var moveOn = function (current, level) {
return new Promise(function (resolve) {
level = level || 0
if (!options.chain) return resolve(null)
// Ideally navigate graph else cheat with URI munging:
var folder =
kb.any(undefined, ns.ldp('contains'), current) || current.dir()
if (!folder) return resolve(null)
kb.fetcher.load(folder).then(function (_xhr) {
var contents = kb.each(folder, ns.ldp('contains')) // @@ load if not loaded
// if (contents.length < 2) return resolve(null) NO might move on from 1-track album
var j
contents.sort() // sort by URI which hopefully will get tracks in order
for (var i = 0; i < contents.length; i++) {
if (current.uri === contents[i].uri) {
j = (i + 1) % contents.length
if (j === 0) {
if (!options.chainAlbums) {
if (options.loop) {
return resolve(contents[j])
}
return resolve(null) // No more music needed
} else {
// chain albums
if (level === 1 || !options.chainAlbums) return resolve(null) // limit of navigating treee
moveOn(folder, level + 1).then(function (folder2) {
if (folder2) {
kb.fetcher.load(folder2).then(function (_xhr) {
var contents = kb.each(folder2, ns.ldp('contains'))
if (contents.length === 0) return resolve(null)
contents.sort()
console.log('New Album: ' + folder2)
return resolve(contents[0]) // Start off new album
})
}
})
}
} else {
return resolve(contents[j])
}
}
} // for
})
})
}
var endedListener = function (event) {
var current = kb.sym(event.target.getAttribute('src'))
if (!options.chain) return
var tryNext = function (cur) {
var current = cur
moveOn(current).then(function (next) {
if (!next) {
console.log('No successor to ' + current)
return
}
if (!looksRedundant(next)) {
console.log('Moving on to ' + next)
guessNames(next)
controlRow.appendChild(audioControl(next, true)) // Force autoplay
controlRow.removeChild(event.target)
} else {
console.log('Ignoring redundant ' + next)
tryNext(next)
}
})
}
tryNext(current)
}
var audioControl = function (song, autoplay) {
var audio = dom.createElement('audio')
audio.setAttribute('controls', 'yes')
audio.setAttribute('src', song.uri)
if (autoplay) {
audio.setAttribute('autoplay', 'autoplay') // Make this a personal preference
}
audio.addEventListener('ended', endedListener, false)
return audio
}
var div = dom.createElement('div')
var table = div.appendChild(dom.createElement('table'))
var labelStyle = 'padding: 0.3em; color:white; background-color: black;'
var artistRow = table.appendChild(dom.createElement('tr'))
artistRow.style.cssText = labelStyle
var albumRow = table.appendChild(dom.createElement('tr'))
albumRow.style.cssText = labelStyle
var trackRow = table.appendChild(dom.createElement('tr'))
trackRow.style.cssText = labelStyle
var controlRow = table.appendChild(dom.createElement('tr'))
guessNames(subject)
controlRow.appendChild(audioControl(subject, options.autoplay))
if (!kb.holds(undefined, ns.ldp('contains'), subject) && subject.dir()) {
kb.fetcher.load(subject.dir()) // Prefetch enclosing @@ or playlist
}
return div
}
}
// ends