WebTorrent can now play audio
This commit is contained in:
@@ -181,7 +181,6 @@ i:not(.disabled):hover {
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 1000;
|
|
||||||
transition: opacity 0.15s ease-out;
|
transition: opacity 0.15s ease-out;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
@@ -456,8 +455,7 @@ input {
|
|||||||
background-color: #F44336;
|
background-color: #F44336;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent.timeout .play,
|
.torrent.timeout .play {
|
||||||
.torrent.unplayable .play {
|
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -738,7 +736,6 @@ body.drag .torrent-placeholder span {
|
|||||||
|
|
||||||
.error-popover {
|
.error-popover {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1001;
|
|
||||||
top: 36px;
|
top: 36px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ function updateElectron () {
|
|||||||
|
|
||||||
// Events from the UI never modify state directly. Instead they call dispatch()
|
// Events from the UI never modify state directly. Instead they call dispatch()
|
||||||
function dispatch (action, ...args) {
|
function dispatch (action, ...args) {
|
||||||
if (['videoMouseMoved', 'playbackJump'].indexOf(action) === -1) {
|
if (['mediaMouseMoved', 'playbackJump'].indexOf(action) === -1) {
|
||||||
console.log('dispatch: %s %o', action, args) /* log user interactions, but don't spam */
|
console.log('dispatch: %s %o', action, args) /* log user interactions, but don't spam */
|
||||||
}
|
}
|
||||||
if (action === 'onOpen') {
|
if (action === 'onOpen') {
|
||||||
@@ -235,20 +235,20 @@ function dispatch (action, ...args) {
|
|||||||
if (action === 'playbackJump') {
|
if (action === 'playbackJump') {
|
||||||
jumpToTime(args[0] /* seconds */)
|
jumpToTime(args[0] /* seconds */)
|
||||||
}
|
}
|
||||||
if (action === 'videoPlaying') {
|
if (action === 'mediaPlaying') {
|
||||||
state.video.isPaused = false
|
state.playing.isPaused = false
|
||||||
ipcRenderer.send('blockPowerSave')
|
ipcRenderer.send('blockPowerSave')
|
||||||
}
|
}
|
||||||
if (action === 'videoPaused') {
|
if (action === 'mediaPaused') {
|
||||||
state.video.isPaused = true
|
state.playing.isPaused = true
|
||||||
ipcRenderer.send('unblockPowerSave')
|
ipcRenderer.send('unblockPowerSave')
|
||||||
}
|
}
|
||||||
if (action === 'toggleFullScreen') {
|
if (action === 'toggleFullScreen') {
|
||||||
ipcRenderer.send('toggleFullScreen', args[0])
|
ipcRenderer.send('toggleFullScreen', args[0])
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
if (action === 'videoMouseMoved') {
|
if (action === 'mediaMouseMoved') {
|
||||||
state.video.mouseStationarySince = new Date().getTime()
|
state.playing.mouseStationarySince = new Date().getTime()
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
if (action === 'exitModal') {
|
if (action === 'exitModal') {
|
||||||
@@ -259,14 +259,14 @@ function dispatch (action, ...args) {
|
|||||||
|
|
||||||
// Plays or pauses the video. If isPaused is undefined, acts as a toggle
|
// Plays or pauses the video. If isPaused is undefined, acts as a toggle
|
||||||
function playPause (isPaused) {
|
function playPause (isPaused) {
|
||||||
if (isPaused === state.video.isPaused) {
|
if (isPaused === state.playing.isPaused) {
|
||||||
return // Nothing to do
|
return // Nothing to do
|
||||||
}
|
}
|
||||||
// Either isPaused is undefined, or it's the opposite of the current state. Toggle.
|
// Either isPaused is undefined, or it's the opposite of the current state. Toggle.
|
||||||
if (Cast.isCasting()) {
|
if (Cast.isCasting()) {
|
||||||
Cast.playPause()
|
Cast.playPause()
|
||||||
}
|
}
|
||||||
state.video.isPaused = !state.video.isPaused
|
state.playing.isPaused = !state.playing.isPaused
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ function jumpToTime (time) {
|
|||||||
if (Cast.isCasting()) {
|
if (Cast.isCasting()) {
|
||||||
Cast.seek(time)
|
Cast.seek(time)
|
||||||
} else {
|
} else {
|
||||||
state.video.jumpToTime = time
|
state.playing.jumpToTime = time
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,20 +601,14 @@ function startServer (torrentSummary, index, cb) {
|
|||||||
|
|
||||||
function startServerFromReadyTorrent (torrent, index, cb) {
|
function startServerFromReadyTorrent (torrent, index, cb) {
|
||||||
// automatically choose which file in the torrent to play, if necessary
|
// automatically choose which file in the torrent to play, if necessary
|
||||||
if (!index) {
|
if (index === undefined) index = pickFileToPlay(torrent.files)
|
||||||
// filter out file formats that the <video> tag definitely can't play
|
if (index === undefined) return cb(new errors.UnplayableError())
|
||||||
var files = torrent.files.filter(TorrentPlayer.isPlayable)
|
var file = torrent.files[index]
|
||||||
if (files.length === 0) return cb(new errors.UnplayableError())
|
|
||||||
// use largest file
|
|
||||||
var largestFile = files.reduce(function (a, b) {
|
|
||||||
return a.length > b.length ? a : b
|
|
||||||
})
|
|
||||||
index = torrent.files.indexOf(largestFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
state.playing.infoHash = torrent.infoHash
|
state.playing.infoHash = torrent.infoHash
|
||||||
state.playing.fileIndex = index
|
state.playing.fileIndex = index
|
||||||
|
state.playing.type = TorrentPlayer.isVideo(file) ? 'video' : 'audio'
|
||||||
|
|
||||||
var server = torrent.createServer()
|
var server = torrent.createServer()
|
||||||
server.listen(0, function () {
|
server.listen(0, function () {
|
||||||
@@ -629,6 +623,28 @@ function startServerFromReadyTorrent (torrent, index, cb) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Picks the default file to play from a list of torrent or torrentSummary files
|
||||||
|
// Returns an index or undefined, if no files are playable
|
||||||
|
function pickFileToPlay (files) {
|
||||||
|
// first, try to find the biggest video file
|
||||||
|
var videoFiles = files.filter(TorrentPlayer.isVideo)
|
||||||
|
if (videoFiles.length > 0) {
|
||||||
|
var largestVideoFile = videoFiles.reduce(function (a, b) {
|
||||||
|
return a.length > b.length ? a : b
|
||||||
|
})
|
||||||
|
return files.indexOf(largestVideoFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no videos, play the first audio file
|
||||||
|
var audioFiles = files.filter(TorrentPlayer.isAudio)
|
||||||
|
if (audioFiles.length > 0) {
|
||||||
|
return files.indexOf(audioFiles[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// no video or audio means nothing is playable
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
function stopServer () {
|
function stopServer () {
|
||||||
if (!state.server) return
|
if (!state.server) return
|
||||||
state.server.server.destroy()
|
state.server.server.destroy()
|
||||||
|
|||||||
@@ -57,14 +57,14 @@ function pollCastStatus (state) {
|
|||||||
if (state.playing.location === 'chromecast') {
|
if (state.playing.location === 'chromecast') {
|
||||||
state.devices.chromecast.status(function (err, status) {
|
state.devices.chromecast.status(function (err, status) {
|
||||||
if (err) return console.log('Error getting %s status: %o', state.playing.location, err)
|
if (err) return console.log('Error getting %s status: %o', state.playing.location, err)
|
||||||
state.video.isPaused = status.playerState === 'PAUSED'
|
state.playing.isPaused = status.playerState === 'PAUSED'
|
||||||
state.video.currentTime = status.currentTime
|
state.playing.currentTime = status.currentTime
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
} else if (state.playing.location === 'airplay') {
|
} else if (state.playing.location === 'airplay') {
|
||||||
state.devices.airplay.status(function (status) {
|
state.devices.airplay.status(function (status) {
|
||||||
state.video.isPaused = status.rate === 0
|
state.playing.isPaused = status.rate === 0
|
||||||
state.video.currentTime = status.position
|
state.playing.currentTime = status.position
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ function stopCasting () {
|
|||||||
|
|
||||||
function stoppedCasting () {
|
function stoppedCasting () {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
state.video.jumpToTime = state.video.currentTime
|
state.playing.jumpToTime = state.playing.currentTime
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,11 +137,11 @@ function playPause () {
|
|||||||
var device
|
var device
|
||||||
if (state.playing.location === 'chromecast') {
|
if (state.playing.location === 'chromecast') {
|
||||||
device = state.devices.chromecast
|
device = state.devices.chromecast
|
||||||
if (!state.video.isPaused) device.pause(castCallback)
|
if (!state.playing.isPaused) device.pause(castCallback)
|
||||||
else device.play(null, null, castCallback)
|
else device.play(null, null, castCallback)
|
||||||
} else if (state.playing.location === 'airplay') {
|
} else if (state.playing.location === 'airplay') {
|
||||||
device = state.devices.airplay
|
device = state.devices.airplay
|
||||||
if (!state.video.isPaused) device.rate(0, castCallback)
|
if (!state.playing.isPaused) device.rate(0, castCallback)
|
||||||
else device.rate(1, castCallback)
|
else device.rate(1, castCallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
isPlayable: isPlayable
|
isPlayable,
|
||||||
|
isVideo,
|
||||||
|
isAudio
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
@@ -8,6 +10,15 @@ var path = require('path')
|
|||||||
* Determines whether a file in a torrent is audio/video we can play
|
* Determines whether a file in a torrent is audio/video we can play
|
||||||
*/
|
*/
|
||||||
function isPlayable (file) {
|
function isPlayable (file) {
|
||||||
var extname = path.extname(file.name)
|
return isVideo(file) || isAudio(file)
|
||||||
return ['.mp4', '.m4v', '.webm', '.mov', '.mkv'].indexOf(extname) !== -1
|
}
|
||||||
|
|
||||||
|
function isVideo (file) {
|
||||||
|
var ext = path.extname(file.name)
|
||||||
|
return ['.mp4', '.m4v', '.webm', '.mov', '.mkv'].indexOf(ext) !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAudio (file) {
|
||||||
|
var ext = path.extname(file.name)
|
||||||
|
return ['.mp3', '.aac', '.ogg', '.wav'].indexOf(ext) !== -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,21 +20,20 @@ module.exports = {
|
|||||||
title: config.APP_NAME /* current window title */
|
title: config.APP_NAME /* current window title */
|
||||||
},
|
},
|
||||||
selectedInfoHash: null, /* the torrent we've selected to view details. see state.torrents */
|
selectedInfoHash: null, /* the torrent we've selected to view details. see state.torrents */
|
||||||
playing: { /* the torrent and file we're currently streaming */
|
playing: { /* the media (audio or video) that we're currently playing */
|
||||||
infoHash: null, /* the info hash of the torrent we're playing */
|
infoHash: null, /* the info hash of the torrent we're playing */
|
||||||
fileIndex: null, /* the zero-based index within the torrent */
|
fileIndex: null, /* the zero-based index within the torrent */
|
||||||
location: 'local' /* 'local', 'chromecast', 'airplay' */
|
location: 'local', /* 'local', 'chromecast', 'airplay' */
|
||||||
},
|
type: null, /* 'audio' or 'video' */
|
||||||
devices: { /* playback devices like Chromecast and AppleTV */
|
|
||||||
airplay: null, /* airplay client. finds and manages AppleTVs */
|
|
||||||
chromecast: null /* chromecast client. finds and manages Chromecasts */
|
|
||||||
},
|
|
||||||
video: { /* state of the video player screen */
|
|
||||||
currentTime: 0, /* seconds */
|
currentTime: 0, /* seconds */
|
||||||
duration: 1, /* seconds */
|
duration: 1, /* seconds */
|
||||||
isPaused: true,
|
isPaused: true,
|
||||||
mouseStationarySince: 0 /* Unix time in ms */
|
mouseStationarySince: 0 /* Unix time in ms */
|
||||||
},
|
},
|
||||||
|
devices: { /* playback devices like Chromecast and AppleTV */
|
||||||
|
airplay: null, /* airplay client. finds and manages AppleTVs */
|
||||||
|
chromecast: null /* chromecast client. finds and manages Chromecasts */
|
||||||
|
},
|
||||||
dock: {
|
dock: {
|
||||||
badge: 0,
|
badge: 0,
|
||||||
progress: 0
|
progress: 0
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ function App (state, dispatch) {
|
|||||||
// * The video is paused
|
// * The video is paused
|
||||||
// * The video is playing remotely on Chromecast or Airplay
|
// * The video is playing remotely on Chromecast or Airplay
|
||||||
var hideControls = state.location.current().url === 'player' &&
|
var hideControls = state.location.current().url === 'player' &&
|
||||||
state.video.mouseStationarySince !== 0 &&
|
state.playing.mouseStationarySince !== 0 &&
|
||||||
new Date().getTime() - state.video.mouseStationarySince > 2000 &&
|
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
|
||||||
!state.video.isPaused &&
|
!state.playing.isPaused &&
|
||||||
state.playing.location === 'local'
|
state.playing.location === 'local'
|
||||||
|
|
||||||
// Hide the header on Windows/Linux when in the player
|
// Hide the header on Windows/Linux when in the player
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ var h = require('virtual-dom/h')
|
|||||||
var hyperx = require('hyperx')
|
var hyperx = require('hyperx')
|
||||||
var hx = hyperx(h)
|
var hx = hyperx(h)
|
||||||
|
|
||||||
|
|
||||||
// Shows a streaming video player. Standard features + Chromecast + Airplay
|
// Shows a streaming video player. Standard features + Chromecast + Airplay
|
||||||
function Player (state, dispatch) {
|
function Player (state, dispatch) {
|
||||||
// Show the video as large as will fit in the window, play immediately
|
// Show the video as large as will fit in the window, play immediately
|
||||||
@@ -12,50 +13,65 @@ function Player (state, dispatch) {
|
|||||||
return hx`
|
return hx`
|
||||||
<div
|
<div
|
||||||
class='player'
|
class='player'
|
||||||
onmousemove=${() => dispatch('videoMouseMoved')}>
|
onmousemove=${() => dispatch('mediaMouseMoved')}>
|
||||||
${showVideo ? renderVideo(state, dispatch) : renderCastScreen(state, dispatch)}
|
${showVideo ? renderMedia(state, dispatch) : renderCastScreen(state, dispatch)}
|
||||||
${renderPlayerControls(state, dispatch)}
|
${renderPlayerControls(state, dispatch)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderVideo (state, dispatch) {
|
function renderMedia (state, dispatch) {
|
||||||
// Unfortunately, play/pause can't be done just by modifying HTML.
|
// Unfortunately, play/pause can't be done just by modifying HTML.
|
||||||
// Instead, grab the DOM node and play/pause it if necessary
|
// Instead, grab the DOM node and play/pause it if necessary
|
||||||
var videoElement = document.querySelector('video')
|
var mediaType = state.playing.type /* 'audio' or 'video' */
|
||||||
if (videoElement !== null) {
|
var mediaElement = document.querySelector(mediaType) /* get the <video> or <audio> tag */
|
||||||
if (state.video.isPaused && !videoElement.paused) {
|
if (mediaElement !== null) {
|
||||||
videoElement.pause()
|
if (state.playing.isPaused && !mediaElement.paused) {
|
||||||
} else if (!state.video.isPaused && videoElement.paused) {
|
mediaElement.pause()
|
||||||
videoElement.play()
|
} else if (!state.playing.isPaused && mediaElement.paused) {
|
||||||
|
mediaElement.play()
|
||||||
}
|
}
|
||||||
// When the user clicks or drags on the progress bar, jump to that position
|
// When the user clicks or drags on the progress bar, jump to that position
|
||||||
if (state.video.jumpToTime) {
|
if (state.playing.jumpToTime) {
|
||||||
videoElement.currentTime = state.video.jumpToTime
|
mediaElement.currentTime = state.playing.jumpToTime
|
||||||
state.video.jumpToTime = null
|
state.playing.jumpToTime = null
|
||||||
}
|
}
|
||||||
state.video.currentTime = videoElement.currentTime
|
state.playing.currentTime = mediaElement.currentTime
|
||||||
state.video.duration = videoElement.duration
|
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`
|
return hx`
|
||||||
<div
|
<div
|
||||||
class='letterbox'
|
class='letterbox'
|
||||||
onmousemove=${() => dispatch('videoMouseMoved')}>
|
style=${style}
|
||||||
<video
|
onmousemove=${() => dispatch('mediaMouseMoved')}>
|
||||||
src='${state.server.localURL}'
|
${mediaTag}
|
||||||
ondblclick=${() => dispatch('toggleFullScreen')}
|
|
||||||
onloadedmetadata=${onLoadedMetadata}
|
|
||||||
onended=${onEnded}
|
|
||||||
onplay=${() => dispatch('videoPlaying')}
|
|
||||||
onpause=${() => dispatch('videoPaused')}
|
|
||||||
autoplay>
|
|
||||||
</video>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
// As soon as the video loads enough to know the video dimensions, resize the window
|
// As soon as the video loads enough to know the video dimensions, resize the window
|
||||||
function onLoadedMetadata (e) {
|
function onLoadedMetadata (e) {
|
||||||
|
if (mediaType !== 'video') return
|
||||||
var video = e.target
|
var video = e.target
|
||||||
var dimensions = {
|
var dimensions = {
|
||||||
width: video.videoWidth,
|
width: video.videoWidth,
|
||||||
@@ -66,7 +82,7 @@ function renderVideo (state, dispatch) {
|
|||||||
|
|
||||||
// When the video completes, pause the video instead of looping
|
// When the video completes, pause the video instead of looping
|
||||||
function onEnded (e) {
|
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')
|
if (!isChromecast && !isAirplay) throw new Error('Unimplemented cast type')
|
||||||
|
|
||||||
// Show a nice title image, if possible
|
// Show a nice title image, if possible
|
||||||
var style = {}
|
var style = {
|
||||||
var infoHash = state.playing.infoHash
|
backgroundImage: cssBackgroundImagePoster(state)
|
||||||
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})`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show whether we're connected to Chromecast / Airplay
|
// 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) {
|
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 playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
|
||||||
|
|
||||||
var elements = [
|
var elements = [
|
||||||
@@ -174,7 +197,7 @@ function renderPlayerControls (state, dispatch) {
|
|||||||
// Finally, the big button in the center plays or pauses the video
|
// Finally, the big button in the center plays or pauses the video
|
||||||
elements.push(hx`
|
elements.push(hx`
|
||||||
<i class='icon play-pause' onclick=${() => dispatch('playPause')}>
|
<i class='icon play-pause' onclick=${() => dispatch('playPause')}>
|
||||||
${state.video.isPaused ? 'play_arrow' : 'pause'}
|
${state.playing.isPaused ? 'play_arrow' : 'pause'}
|
||||||
</i>
|
</i>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
@@ -182,10 +205,10 @@ function renderPlayerControls (state, dispatch) {
|
|||||||
|
|
||||||
// Handles a click or drag to scrub (jump to another position in the video)
|
// Handles a click or drag to scrub (jump to another position in the video)
|
||||||
function handleScrub (e) {
|
function handleScrub (e) {
|
||||||
dispatch('videoMouseMoved')
|
dispatch('mediaMouseMoved')
|
||||||
var windowWidth = document.querySelector('body').clientWidth
|
var windowWidth = document.querySelector('body').clientWidth
|
||||||
var fraction = e.clientX / windowWidth
|
var fraction = e.clientX / windowWidth
|
||||||
var position = fraction * state.video.duration /* seconds */
|
var position = fraction * state.playing.duration /* seconds */
|
||||||
dispatch('playbackJump', position)
|
dispatch('playbackJump', position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,9 +103,10 @@ function TorrentList (state, dispatch) {
|
|||||||
// Download button toggles between torrenting (DL/seed) and paused
|
// Download button toggles between torrenting (DL/seed) and paused
|
||||||
// Play button starts streaming the torrent immediately, unpausing if needed
|
// Play button starts streaming the torrent immediately, unpausing if needed
|
||||||
function renderTorrentButtons (torrentSummary) {
|
function renderTorrentButtons (torrentSummary) {
|
||||||
var playIcon, playTooltip
|
var playIcon, playTooltip, playClass
|
||||||
if (torrentSummary.playStatus === 'unplayable') {
|
if (torrentSummary.playStatus === 'unplayable') {
|
||||||
playIcon = 'warning'
|
playIcon = 'play_arrow'
|
||||||
|
playClass = 'disabled'
|
||||||
playTooltip = 'Sorry, WebTorrent can\'t play any of the files in this torrent. ' +
|
playTooltip = 'Sorry, WebTorrent can\'t play any of the files in this torrent. ' +
|
||||||
'View details and click on individual files to open them in another program.'
|
'View details and click on individual files to open them in another program.'
|
||||||
} else if (torrentSummary.playStatus === 'timeout') {
|
} else if (torrentSummary.playStatus === 'timeout') {
|
||||||
@@ -131,13 +132,14 @@ function TorrentList (state, dispatch) {
|
|||||||
return hx`
|
return hx`
|
||||||
<div class='buttons'>
|
<div class='buttons'>
|
||||||
<i.btn.icon.play
|
<i.btn.icon.play
|
||||||
title='${playTooltip}'
|
title=${playTooltip}
|
||||||
|
class=${playClass}
|
||||||
onclick=${(e) => handleButton('play', e)}>
|
onclick=${(e) => handleButton('play', e)}>
|
||||||
${playIcon}
|
${playIcon}
|
||||||
</i>
|
</i>
|
||||||
<i.btn.icon.download
|
<i.btn.icon.download
|
||||||
class='${torrentSummary.status}'
|
class=${torrentSummary.status}
|
||||||
title='${downloadTooltip}'
|
title=${downloadTooltip}
|
||||||
onclick=${(e) => handleButton('toggleTorrent', e)}>
|
onclick=${(e) => handleButton('toggleTorrent', e)}>
|
||||||
${downloadIcon}
|
${downloadIcon}
|
||||||
</i>
|
</i>
|
||||||
@@ -153,6 +155,7 @@ function TorrentList (state, dispatch) {
|
|||||||
function handleButton (action, e) {
|
function handleButton (action, e) {
|
||||||
// Prevent propagation so that we don't select/unselect the torrent
|
// Prevent propagation so that we don't select/unselect the torrent
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
if (e.target.classList.contains('disabled')) return
|
||||||
dispatch(action, torrentSummary)
|
dispatch(action, torrentSummary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user