Play unsupported files in VLC
This commit is contained in:
60
main/ipc.js
60
main/ipc.js
@@ -2,6 +2,7 @@ module.exports = {
|
|||||||
init
|
init
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cp = require('child_process')
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
|
|
||||||
var app = electron.app
|
var app = electron.app
|
||||||
@@ -16,6 +17,12 @@ var shortcuts = require('./shortcuts')
|
|||||||
// has to be a number, not a boolean, and undefined throws an error
|
// has to be a number, not a boolean, and undefined throws an error
|
||||||
var powerSaveBlockID = 0
|
var powerSaveBlockID = 0
|
||||||
|
|
||||||
|
// messages from the main process, to be sent once the WebTorrent process starts
|
||||||
|
var messageQueueMainToWebTorrent = []
|
||||||
|
|
||||||
|
// holds a ChildProcess while we're playing a video in VLC, null otherwise
|
||||||
|
var vlcProcess
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
ipcMain.on('ipcReady', function (e) {
|
ipcMain.on('ipcReady', function (e) {
|
||||||
app.ipcReady = true
|
app.ipcReady = true
|
||||||
@@ -24,7 +31,6 @@ function init () {
|
|||||||
console.timeEnd('init')
|
console.timeEnd('init')
|
||||||
})
|
})
|
||||||
|
|
||||||
var messageQueueMainToWebTorrent = []
|
|
||||||
ipcMain.on('ipcReadyWebTorrent', function (e) {
|
ipcMain.on('ipcReadyWebTorrent', function (e) {
|
||||||
app.ipcReadyWebTorrent = true
|
app.ipcReadyWebTorrent = true
|
||||||
log('sending %d queued messages from the main win to the webtorrent window',
|
log('sending %d queued messages from the main win to the webtorrent window',
|
||||||
@@ -36,6 +42,7 @@ function init () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('showOpenTorrentFile', menu.showOpenTorrentFile)
|
ipcMain.on('showOpenTorrentFile', menu.showOpenTorrentFile)
|
||||||
|
|
||||||
ipcMain.on('showOpenSeedFiles', menu.showOpenSeedFiles)
|
ipcMain.on('showOpenSeedFiles', menu.showOpenSeedFiles)
|
||||||
|
|
||||||
ipcMain.on('setBounds', function (e, bounds, maximize) {
|
ipcMain.on('setBounds', function (e, bounds, maximize) {
|
||||||
@@ -68,12 +75,14 @@ function init () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('blockPowerSave', blockPowerSave)
|
ipcMain.on('blockPowerSave', blockPowerSave)
|
||||||
|
|
||||||
ipcMain.on('unblockPowerSave', unblockPowerSave)
|
ipcMain.on('unblockPowerSave', unblockPowerSave)
|
||||||
|
|
||||||
ipcMain.on('onPlayerOpen', function () {
|
ipcMain.on('onPlayerOpen', function () {
|
||||||
menu.onPlayerOpen()
|
menu.onPlayerOpen()
|
||||||
shortcuts.registerPlayerShortcuts()
|
shortcuts.registerPlayerShortcuts()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('onPlayerClose', function () {
|
ipcMain.on('onPlayerClose', function () {
|
||||||
menu.onPlayerClose()
|
menu.onPlayerClose()
|
||||||
shortcuts.unregisterPlayerShortcuts()
|
shortcuts.unregisterPlayerShortcuts()
|
||||||
@@ -83,6 +92,55 @@ function init () {
|
|||||||
windows.focusWindow(windows[windowName])
|
windows.focusWindow(windows[windowName])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('vlcVersion', function (e) {
|
||||||
|
cp.exec('vlc --version', function (e, stdout, stderr) {
|
||||||
|
var version
|
||||||
|
if (e) {
|
||||||
|
version = null
|
||||||
|
} else {
|
||||||
|
// Prints several lines, starting with eg: VLC media player 2.7.0
|
||||||
|
if (!stdout.startsWith('VLC media player')) version = 'unknown'
|
||||||
|
else version = stdout.split(' ')[3]
|
||||||
|
}
|
||||||
|
windows.main.send('vlcVersion', version)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('vlcPlay', function (e, url) {
|
||||||
|
// TODO: cross-platform VLC detection
|
||||||
|
var command = 'vlc'
|
||||||
|
var args = ['--play-and-exit', '--quiet', url]
|
||||||
|
console.log('Running ' + command + ' ' + args.join(' '))
|
||||||
|
|
||||||
|
vlcProcess = cp.spawn(command, args)
|
||||||
|
|
||||||
|
// If it works, close the modal after a second
|
||||||
|
var closeModalTimeout = setTimeout(() => windows.main.send('dispatch', 'exitModal'), 1000)
|
||||||
|
|
||||||
|
vlcProcess.on('close', function (code) {
|
||||||
|
clearTimeout(closeModalTimeout)
|
||||||
|
if (!vlcProcess) return // Killed
|
||||||
|
console.log('VLC exited with code ', code)
|
||||||
|
if (code === 0) {
|
||||||
|
windows.main.send('dispatch', 'backToList')
|
||||||
|
} else {
|
||||||
|
windows.main.send('dispatch', 'vlcNotFound')
|
||||||
|
}
|
||||||
|
vlcProcess = null
|
||||||
|
})
|
||||||
|
|
||||||
|
vlcProcess.on('error', function (e) {
|
||||||
|
console.log('VLC error', e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('vlcQuit', function () {
|
||||||
|
if (!vlcProcess) return
|
||||||
|
console.log('Killing VLC, pid ' + vlcProcess.pid)
|
||||||
|
vlcProcess.kill('SIGKILL') // kill -9
|
||||||
|
vlcProcess = null
|
||||||
|
})
|
||||||
|
|
||||||
// Capture all events
|
// Capture all events
|
||||||
var oldEmit = ipcMain.emit
|
var oldEmit = ipcMain.emit
|
||||||
ipcMain.emit = function (name, e, ...args) {
|
ipcMain.emit = function (name, e, ...args) {
|
||||||
|
|||||||
@@ -987,3 +987,7 @@ body.drag .app::after {
|
|||||||
.error-popover .error:last-child {
|
.error-popover .error:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #c44;
|
||||||
|
}
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ function dispatch (action, ...args) {
|
|||||||
setDimensions(args[0] /* dimensions */)
|
setDimensions(args[0] /* dimensions */)
|
||||||
}
|
}
|
||||||
if (action === 'backToList') {
|
if (action === 'backToList') {
|
||||||
|
// Exit any modals and screens with a back button
|
||||||
|
state.modal = null
|
||||||
while (state.location.hasBack()) state.location.back()
|
while (state.location.hasBack()) state.location.back()
|
||||||
|
|
||||||
// Work around virtual-dom issue: it doesn't expose its redraw function,
|
// Work around virtual-dom issue: it doesn't expose its redraw function,
|
||||||
@@ -302,20 +304,38 @@ function dispatch (action, ...args) {
|
|||||||
state.playing.isStalled = true
|
state.playing.isStalled = true
|
||||||
}
|
}
|
||||||
if (action === 'mediaError') {
|
if (action === 'mediaError') {
|
||||||
state.location.back(function () {
|
if (state.location.current().url === 'player') {
|
||||||
onError(new Error('Unsupported file format'))
|
state.playing.location = 'error'
|
||||||
})
|
ipcRenderer.send('vlcVersion')
|
||||||
|
ipcRenderer.once('vlcVersion', function (e, version) {
|
||||||
|
console.log('vlcVersion', version)
|
||||||
|
state.modal = {
|
||||||
|
id: 'unsupported-media-modal',
|
||||||
|
error: args[0],
|
||||||
|
vlcInstalled: !!version
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (action === 'mediaTimeUpdate') {
|
if (action === 'mediaTimeUpdate') {
|
||||||
state.playing.lastTimeUpdate = new Date().getTime()
|
state.playing.lastTimeUpdate = new Date().getTime()
|
||||||
state.playing.isStalled = false
|
state.playing.isStalled = false
|
||||||
}
|
}
|
||||||
if (action === 'toggleFullScreen') {
|
|
||||||
ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */)
|
|
||||||
}
|
|
||||||
if (action === 'mediaMouseMoved') {
|
if (action === 'mediaMouseMoved') {
|
||||||
state.playing.mouseStationarySince = new Date().getTime()
|
state.playing.mouseStationarySince = new Date().getTime()
|
||||||
}
|
}
|
||||||
|
if (action === 'vlcPlay') {
|
||||||
|
ipcRenderer.send('vlcPlay', state.server.localURL)
|
||||||
|
state.playing.location = 'vlc'
|
||||||
|
}
|
||||||
|
if (action === 'vlcNotFound') {
|
||||||
|
if (state.modal && state.modal.id === 'unsupported-media-modal') {
|
||||||
|
state.modal.vlcNotFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (action === 'toggleFullScreen') {
|
||||||
|
ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */)
|
||||||
|
}
|
||||||
if (action === 'exitModal') {
|
if (action === 'exitModal') {
|
||||||
state.modal = null
|
state.modal = null
|
||||||
}
|
}
|
||||||
@@ -624,6 +644,7 @@ function startTorrentingSummary (torrentSummary) {
|
|||||||
torrentID = s.magnetURI || s.infoHash
|
torrentID = s.magnetURI || s.infoHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('start torrenting %s %s', s.torrentKey, torrentID)
|
||||||
ipcRenderer.send('wt-start-torrenting', s.torrentKey, torrentID, path, s.fileModtimes)
|
ipcRenderer.send('wt-start-torrenting', s.torrentKey, torrentID, path, s.fileModtimes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,6 +929,9 @@ function closePlayer (cb) {
|
|||||||
if (isCasting()) {
|
if (isCasting()) {
|
||||||
Cast.close()
|
Cast.close()
|
||||||
}
|
}
|
||||||
|
if (state.playing.location === 'vlc') {
|
||||||
|
ipcRenderer.send('vlcQuit')
|
||||||
|
}
|
||||||
state.window.title = config.APP_WINDOW_TITLE
|
state.window.title = config.APP_WINDOW_TITLE
|
||||||
state.playing = State.getDefaultPlayState()
|
state.playing = State.getDefaultPlayState()
|
||||||
state.server = null
|
state.server = null
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ var Views = {
|
|||||||
}
|
}
|
||||||
var Modals = {
|
var Modals = {
|
||||||
'open-torrent-address-modal': require('./open-torrent-address-modal'),
|
'open-torrent-address-modal': require('./open-torrent-address-modal'),
|
||||||
'update-available-modal': require('./update-available-modal')
|
'update-available-modal': require('./update-available-modal'),
|
||||||
|
'unsupported-media-modal': require('./unsupported-media-modal')
|
||||||
}
|
}
|
||||||
|
|
||||||
function App (state) {
|
function App (state) {
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ function renderMedia (state) {
|
|||||||
onstalling=${dispatcher('mediaStalled')}
|
onstalling=${dispatcher('mediaStalled')}
|
||||||
onerror=${dispatcher('mediaError')}
|
onerror=${dispatcher('mediaError')}
|
||||||
ontimeupdate=${dispatcher('mediaTimeUpdate')}
|
ontimeupdate=${dispatcher('mediaTimeUpdate')}
|
||||||
autoplay>
|
onencrypted=${dispatcher('mediaEncrypted')}
|
||||||
|
oncanplay=${onCanPlay}>
|
||||||
${trackTags}
|
${trackTags}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@@ -120,6 +121,16 @@ function renderMedia (state) {
|
|||||||
function onEnded (e) {
|
function onEnded (e) {
|
||||||
state.playing.isPaused = true
|
state.playing.isPaused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onCanPlay (e) {
|
||||||
|
var video = e.target
|
||||||
|
if (video.webkitVideoDecodedByteCount > 0 &&
|
||||||
|
video.webkitAudioDecodedByteCount === 0) {
|
||||||
|
dispatch('mediaError', 'Audio codec unsupported')
|
||||||
|
} else {
|
||||||
|
video.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOverlay (state) {
|
function renderOverlay (state) {
|
||||||
@@ -207,20 +218,33 @@ function renderLoadingSpinner (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderCastScreen (state) {
|
function renderCastScreen (state) {
|
||||||
var castIcon, castType
|
var castIcon, castType, isCast
|
||||||
if (state.playing.location.startsWith('chromecast')) {
|
if (state.playing.location.startsWith('chromecast')) {
|
||||||
castIcon = 'cast_connected'
|
castIcon = 'cast_connected'
|
||||||
castType = 'Chromecast'
|
castType = 'Chromecast'
|
||||||
|
isCast = true
|
||||||
} else if (state.playing.location.startsWith('airplay')) {
|
} else if (state.playing.location.startsWith('airplay')) {
|
||||||
castIcon = 'airplay'
|
castIcon = 'airplay'
|
||||||
castType = 'AirPlay'
|
castType = 'AirPlay'
|
||||||
|
isCast = true
|
||||||
} else if (state.playing.location.startsWith('dlna')) {
|
} else if (state.playing.location.startsWith('dlna')) {
|
||||||
castIcon = 'tv'
|
castIcon = 'tv'
|
||||||
castType = 'DLNA'
|
castType = 'DLNA'
|
||||||
|
isCast = true
|
||||||
|
} else if (state.playing.location === 'vlc') {
|
||||||
|
castIcon = 'tv'
|
||||||
|
castType = 'VLC'
|
||||||
|
isCast = false
|
||||||
|
} else if (state.playing.location === 'error') {
|
||||||
|
castIcon = 'error_outline'
|
||||||
|
castType = 'Error'
|
||||||
|
isCast = false
|
||||||
}
|
}
|
||||||
|
|
||||||
var isStarting = state.playing.location.endsWith('-pending')
|
var isStarting = state.playing.location.endsWith('-pending')
|
||||||
var castStatus = isStarting ? 'Connecting...' : 'Connected'
|
var castStatus
|
||||||
|
if (isCast) castStatus = isStarting ? 'Connecting...' : 'Connected'
|
||||||
|
else castStatus = ''
|
||||||
|
|
||||||
// Show a nice title image, if possible
|
// Show a nice title image, if possible
|
||||||
var style = {
|
var style = {
|
||||||
@@ -240,15 +264,26 @@ function renderCastScreen (state) {
|
|||||||
|
|
||||||
function renderSubtitlesOptions (state) {
|
function renderSubtitlesOptions (state) {
|
||||||
var subtitles = state.playing.subtitles
|
var subtitles = state.playing.subtitles
|
||||||
if (subtitles.tracks.length && subtitles.show) {
|
if (!subtitles.tracks.length || !subtitles.show) return
|
||||||
return hx`<ul.subtitles-list>
|
|
||||||
${subtitles.tracks.map(function (w, i) {
|
var items = subtitles.tracks.map(function (track) {
|
||||||
return hx`<li onclick=${dispatcher('selectSubtitle', w.label)}><i.icon>${w.selected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>${w.label}</li>`
|
return hx`
|
||||||
})}
|
<li onclick=${dispatcher('selectSubtitle', track.label)}>
|
||||||
<li onclick=${dispatcher('selectSubtitle', '')}><i.icon>${!subtitles.enabled ? 'radio_button_checked' : 'radio_button_unchecked'}</i>None</li>
|
<i.icon>${track.selected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||||
</ul>
|
${track.label}
|
||||||
|
</li>
|
||||||
`
|
`
|
||||||
}
|
})
|
||||||
|
|
||||||
|
return hx`
|
||||||
|
<ul.subtitles-list>
|
||||||
|
${items}
|
||||||
|
<li onclick=${dispatcher('selectSubtitle', '')}>
|
||||||
|
<i.icon>${!subtitles.enabled ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||||
|
None
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPlayerControls (state) {
|
function renderPlayerControls (state) {
|
||||||
|
|||||||
42
renderer/views/unsupported-media-modal.js
Normal file
42
renderer/views/unsupported-media-modal.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module.exports = UnsupportedMediaModal
|
||||||
|
|
||||||
|
var h = require('virtual-dom/h')
|
||||||
|
var hyperx = require('hyperx')
|
||||||
|
var hx = hyperx(h)
|
||||||
|
|
||||||
|
var electron = require('electron')
|
||||||
|
|
||||||
|
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||||
|
|
||||||
|
function UnsupportedMediaModal (state) {
|
||||||
|
var err = state.modal.error
|
||||||
|
var message = (err && err.getMessage)
|
||||||
|
? err.getMessage()
|
||||||
|
: err
|
||||||
|
var actionButton = state.modal.vlcInstalled
|
||||||
|
? hx`<button class="button-raised" onclick=${onPlay}>Play in VLC</button>`
|
||||||
|
: hx`<button class="button-raised" onclick=${onInstall}>Install VLC</button>`
|
||||||
|
var vlcMessage = state.modal.vlcNotFound
|
||||||
|
? 'Couldn\'t run VLC. Please make sure it\'s installed.'
|
||||||
|
: ''
|
||||||
|
return hx`
|
||||||
|
<div>
|
||||||
|
<p><strong>Sorry, we can't play that file.</strong></p>
|
||||||
|
<p>${message}</p>
|
||||||
|
<p class='float-right'>
|
||||||
|
<button class="button-flat" onclick=${dispatcher('backToList')}>Cancel</button>
|
||||||
|
${actionButton}
|
||||||
|
</p>
|
||||||
|
<p class='error-text'>${vlcMessage}</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
function onInstall () {
|
||||||
|
electron.shell.openExternal('http://www.videolan.org/vlc/')
|
||||||
|
state.modal.vlcInstalled = true // Assume they'll install it successfully
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPlay () {
|
||||||
|
dispatch('vlcPlay')
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user