WebTorrent can now play audio

This commit is contained in:
DC
2016-03-22 02:07:57 -07:00
parent f7029c811c
commit ebcc814ca7
8 changed files with 134 additions and 85 deletions

View File

@@ -4,6 +4,7 @@ var h = require('virtual-dom/h')
var hyperx = require('hyperx')
var hx = hyperx(h)
// Shows a streaming video player. Standard features + Chromecast + Airplay
function Player (state, dispatch) {
// Show the video as large as will fit in the window, play immediately
@@ -12,50 +13,65 @@ function Player (state, dispatch) {
return hx`
<div
class='player'
onmousemove=${() => dispatch('videoMouseMoved')}>
${showVideo ? renderVideo(state, dispatch) : renderCastScreen(state, dispatch)}
onmousemove=${() => dispatch('mediaMouseMoved')}>
${showVideo ? renderMedia(state, dispatch) : renderCastScreen(state, dispatch)}
${renderPlayerControls(state, dispatch)}
</div>
`
}
function renderVideo (state, dispatch) {
function renderMedia (state, dispatch) {
// Unfortunately, play/pause can't be done just by modifying HTML.
// Instead, grab the DOM node and play/pause it if necessary
var videoElement = document.querySelector('video')
if (videoElement !== null) {
if (state.video.isPaused && !videoElement.paused) {
videoElement.pause()
} else if (!state.video.isPaused && videoElement.paused) {
videoElement.play()
var mediaType = state.playing.type /* 'audio' or 'video' */
var mediaElement = document.querySelector(mediaType) /* get the <video> or <audio> tag */
if (mediaElement !== null) {
if (state.playing.isPaused && !mediaElement.paused) {
mediaElement.pause()
} else if (!state.playing.isPaused && mediaElement.paused) {
mediaElement.play()
}
// When the user clicks or drags on the progress bar, jump to that position
if (state.video.jumpToTime) {
videoElement.currentTime = state.video.jumpToTime
state.video.jumpToTime = null
if (state.playing.jumpToTime) {
mediaElement.currentTime = state.playing.jumpToTime
state.playing.jumpToTime = null
}
state.video.currentTime = videoElement.currentTime
state.video.duration = videoElement.duration
state.playing.currentTime = mediaElement.currentTime
state.playing.duration = mediaElement.duration
}
// Create the <audio> or <video> tag
var mediaTag = hx`
<div
src='${state.server.localURL}'
ondblclick=${() => dispatch('toggleFullScreen')}
onloadedmetadata=${onLoadedMetadata}
onended=${onEnded}
onplay=${() => dispatch('mediaPlaying')}
onpause=${() => dispatch('mediaPaused')}
autoplay>
</div>
`
mediaTag.tagName = mediaType
// Show the media.
// Video fills the window, centered with black bars if necessary
// Audio gets a static poster image and a summary of the file metadata.
var style = {
backgroundImage: mediaType === 'audio' ? cssBackgroundImagePoster(state) : ''
}
return hx`
<div
class='letterbox'
onmousemove=${() => dispatch('videoMouseMoved')}>
<video
src='${state.server.localURL}'
ondblclick=${() => dispatch('toggleFullScreen')}
onloadedmetadata=${onLoadedMetadata}
onended=${onEnded}
onplay=${() => dispatch('videoPlaying')}
onpause=${() => dispatch('videoPaused')}
autoplay>
</video>
style=${style}
onmousemove=${() => dispatch('mediaMouseMoved')}>
${mediaTag}
</div>
`
// As soon as the video loads enough to know the video dimensions, resize the window
function onLoadedMetadata (e) {
if (mediaType !== 'video') return
var video = e.target
var dimensions = {
width: video.videoWidth,
@@ -66,7 +82,7 @@ function renderVideo (state, dispatch) {
// When the video completes, pause the video instead of looping
function onEnded (e) {
state.video.isPaused = true
state.playing.isPaused = true
}
}
@@ -77,12 +93,8 @@ function renderCastScreen (state, dispatch) {
if (!isChromecast && !isAirplay) throw new Error('Unimplemented cast type')
// Show a nice title image, if possible
var style = {}
var infoHash = state.playing.infoHash
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === infoHash)
if (torrentSummary && torrentSummary.posterURL) {
var cleanURL = torrentSummary.posterURL.replace(/\\/g, '/')
style.backgroundImage = `radial-gradient(circle at center, rgba(0,0,0,0.4) 0%,rgba(0,0,0,1) 100%), url(${cleanURL})`
var style = {
backgroundImage: cssBackgroundImagePoster(state)
}
// Show whether we're connected to Chromecast / Airplay
@@ -98,8 +110,19 @@ function renderCastScreen (state, dispatch) {
`
}
// Returns the CSS background-image string for a poster image + dark vignette
function cssBackgroundImagePoster (state) {
var infoHash = state.playing.infoHash
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === infoHash)
if (!torrentSummary || !torrentSummary.posterURL) return ''
var cleanURL = torrentSummary.posterURL.replace(/\\/g, '/')
return 'radial-gradient(circle at center, ' +
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)' +
`, url(${cleanURL})`
}
function renderPlayerControls (state, dispatch) {
var positionPercent = 100 * state.video.currentTime / state.video.duration
var positionPercent = 100 * state.playing.currentTime / state.playing.duration
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
var elements = [
@@ -174,7 +197,7 @@ function renderPlayerControls (state, dispatch) {
// Finally, the big button in the center plays or pauses the video
elements.push(hx`
<i class='icon play-pause' onclick=${() => dispatch('playPause')}>
${state.video.isPaused ? 'play_arrow' : 'pause'}
${state.playing.isPaused ? 'play_arrow' : 'pause'}
</i>
`)
@@ -182,10 +205,10 @@ function renderPlayerControls (state, dispatch) {
// Handles a click or drag to scrub (jump to another position in the video)
function handleScrub (e) {
dispatch('videoMouseMoved')
dispatch('mediaMouseMoved')
var windowWidth = document.querySelector('body').clientWidth
var fraction = e.clientX / windowWidth
var position = fraction * state.video.duration /* seconds */
var position = fraction * state.playing.duration /* seconds */
dispatch('playbackJump', position)
}
}