Issue #92: Merge with master & exclude some of the proposed changes.
This commit is contained in:
@@ -23,7 +23,7 @@ module.exports = {
|
||||
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
||||
TELEMETRY_URL: 'https://webtorrent.io/desktop/telemetry',
|
||||
|
||||
APP_COPYRIGHT: 'Copyright © 2014-2017 ' + APP_TEAM,
|
||||
APP_COPYRIGHT: 'Copyright © 2014-2018 ' + APP_TEAM,
|
||||
APP_FILE_ICON: path.join(__dirname, '..', 'static', 'WebTorrentFile'),
|
||||
APP_ICON: path.join(__dirname, '..', 'static', 'WebTorrent'),
|
||||
APP_NAME: APP_NAME,
|
||||
|
||||
@@ -7,9 +7,8 @@ const electron = require('electron')
|
||||
const config = require('../config')
|
||||
const log = require('./log')
|
||||
|
||||
const ANNOUNCEMENT_URL = config.ANNOUNCEMENT_URL +
|
||||
'?version=' + config.APP_VERSION +
|
||||
'&platform=' + process.platform
|
||||
const ANNOUNCEMENT_URL =
|
||||
`${config.ANNOUNCEMENT_URL}?version=${config.APP_VERSION}&platform=${process.platform}`
|
||||
|
||||
/**
|
||||
* In certain situations, the WebTorrent team may need to show an announcement to
|
||||
|
||||
@@ -29,7 +29,6 @@ function spawn (playerPath, url, title) {
|
||||
if (err) return windows.main.dispatch('externalPlayerNotFound')
|
||||
const args = [
|
||||
'--play-and-exit',
|
||||
'--video-on-top',
|
||||
'--quiet',
|
||||
`--meta-title=${JSON.stringify(title)}`,
|
||||
url
|
||||
@@ -40,13 +39,13 @@ function spawn (playerPath, url, title) {
|
||||
|
||||
function kill () {
|
||||
if (!proc) return
|
||||
log('Killing external player, pid ' + proc.pid)
|
||||
log(`Killing external player, pid ${proc.pid}`)
|
||||
proc.kill('SIGKILL') // kill -9
|
||||
proc = null
|
||||
}
|
||||
|
||||
function spawnExternal (playerPath, args) {
|
||||
log('Running external media player:', playerPath + ' ' + args.join(' '))
|
||||
log('Running external media player:', `${playerPath} ${args.join(' ')}`)
|
||||
|
||||
if (process.platform === 'darwin' && path.extname(playerPath) === '.app') {
|
||||
// Mac: Use executable in packaged .app bundle
|
||||
|
||||
@@ -40,9 +40,9 @@ function installDarwin () {
|
||||
// File handlers are defined in `Info.plist`.
|
||||
}
|
||||
|
||||
function uninstallDarwin () { }
|
||||
function uninstallDarwin () {}
|
||||
|
||||
const EXEC_COMMAND = [process.execPath]
|
||||
const EXEC_COMMAND = [ process.execPath, '--' ]
|
||||
|
||||
if (!config.IS_PRODUCTION) {
|
||||
EXEC_COMMAND.push(config.ROOT_PATH)
|
||||
@@ -312,7 +312,7 @@ function installLinux () {
|
||||
'webtorrent-desktop.desktop'
|
||||
)
|
||||
fs.mkdirp(path.dirname(desktopFilePath))
|
||||
fs.writeFile(desktopFilePath, desktopFile, (err) => {
|
||||
fs.writeFile(desktopFilePath, desktopFile, err => {
|
||||
if (err) return log.error(err.message)
|
||||
})
|
||||
}
|
||||
@@ -334,9 +334,9 @@ function installLinux () {
|
||||
'icons',
|
||||
'webtorrent-desktop.png'
|
||||
)
|
||||
mkdirp(path.dirname(iconFilePath), (err) => {
|
||||
mkdirp(path.dirname(iconFilePath), err => {
|
||||
if (err) return log.error(err.message)
|
||||
fs.writeFile(iconFilePath, iconFile, (err) => {
|
||||
fs.writeFile(iconFilePath, iconFile, err => {
|
||||
if (err) log.error(err.message)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -188,7 +188,7 @@ function onAppOpen (newArgv) {
|
||||
function sliceArgv (argv) {
|
||||
return argv.slice(config.IS_PRODUCTION ? 1
|
||||
: config.IS_TEST ? 4
|
||||
: 2)
|
||||
: 2)
|
||||
}
|
||||
|
||||
function processArgv (argv) {
|
||||
|
||||
@@ -138,7 +138,7 @@ function setAspectRatio (aspectRatio) {
|
||||
function setBounds (bounds, maximize) {
|
||||
// Do nothing in fullscreen
|
||||
if (!main.win || main.win.isFullScreen()) {
|
||||
log('setBounds: not setting bounds because we\'re in full screen')
|
||||
log(`setBounds: not setting bounds because we're in full screen`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -162,13 +162,13 @@ function setBounds (bounds, maximize) {
|
||||
|
||||
// Assuming we're not maximized or maximizing, set the window size
|
||||
if (!willBeMaximized) {
|
||||
log('setBounds: setting bounds to ' + JSON.stringify(bounds))
|
||||
log(`setBounds: setting bounds to ${JSON.stringify(bounds)}`)
|
||||
if (bounds.x === null && bounds.y === null) {
|
||||
// X and Y not specified? By default, center on current screen
|
||||
const scr = electron.screen.getDisplayMatching(main.win.getBounds())
|
||||
bounds.x = Math.round(scr.bounds.x + (scr.bounds.width / 2) - (bounds.width / 2))
|
||||
bounds.y = Math.round(scr.bounds.y + (scr.bounds.height / 2) - (bounds.height / 2))
|
||||
log('setBounds: centered to ' + JSON.stringify(bounds))
|
||||
log(`setBounds: centered to ${JSON.stringify(bounds)}`)
|
||||
}
|
||||
// Resize the window's content area (so window border doesn't need to be taken
|
||||
// into account)
|
||||
|
||||
@@ -102,10 +102,10 @@ module.exports = class PlaybackController {
|
||||
const state = this.state
|
||||
if (Playlist.hasNext(state) && state.playing.location !== 'external') {
|
||||
this.updatePlayer(
|
||||
state.playing.infoHash, Playlist.getNextIndex(state), false, (err) => {
|
||||
if (err) dispatch('error', err)
|
||||
else this.play()
|
||||
})
|
||||
state.playing.infoHash, Playlist.getNextIndex(state), false, (err) => {
|
||||
if (err) dispatch('error', err)
|
||||
else this.play()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ module.exports = class PlaybackController {
|
||||
state.playing.fileIndex = index
|
||||
state.playing.type = TorrentPlayer.isVideo(fileSummary) ? 'video'
|
||||
: TorrentPlayer.isAudio(fileSummary) ? 'audio'
|
||||
: 'other'
|
||||
: 'other'
|
||||
|
||||
// pick up where we left off
|
||||
let jumpToTime = 0
|
||||
|
||||
@@ -157,23 +157,24 @@ module.exports = class TorrentListController {
|
||||
|
||||
prioritizeTorrent (infoHash) {
|
||||
this.state.saved.torrents
|
||||
.filter((torrent) => { // We're interested in active torrents only.
|
||||
return (['downloading', 'seeding'].indexOf(torrent.status) !== -1)
|
||||
})
|
||||
.map((torrent) => { // Pause all active torrents except the one that started playing.
|
||||
if (infoHash === torrent.infoHash) return
|
||||
.filter((torrent) => { // We're interested in active torrents only.
|
||||
return (['downloading', 'seeding'].indexOf(torrent.status) !== -1)
|
||||
})
|
||||
.map((torrent) => { // Pause all active torrents except the one that started playing.
|
||||
if (infoHash === torrent.infoHash) return
|
||||
|
||||
// Pause torrent without playing sounds.
|
||||
this.pauseTorrent(torrent, false)
|
||||
// Pause torrent without playing sounds.
|
||||
this.pauseTorrent(torrent, false)
|
||||
|
||||
this.state.saved.torrentsToResume.push(torrent.infoHash)
|
||||
})
|
||||
this.state.saved.torrentsToResume.push(torrent.infoHash)
|
||||
})
|
||||
|
||||
console.log('Playback Priority: paused torrents: ', this.state.saved.torrentsToResume)
|
||||
}
|
||||
|
||||
resumePausedTorrents () {
|
||||
console.log('Playback Priority: resuming paused torrents')
|
||||
if (!this.state.saved.torrentsToResume || !this.state.saved.torrentsToResume.length) return
|
||||
this.state.saved.torrentsToResume.map((infoHash) => {
|
||||
this.toggleTorrent(infoHash)
|
||||
})
|
||||
|
||||
@@ -123,7 +123,8 @@ function setupStateSaved (cb) {
|
||||
externalPlayerPath: null,
|
||||
startup: false,
|
||||
autoAddTorrents: false,
|
||||
torrentsFolderPath: ''
|
||||
torrentsFolderPath: '',
|
||||
highestPlaybackPriority: true
|
||||
},
|
||||
torrents: config.DEFAULT_TORRENTS.map(createTorrentObject),
|
||||
torrentsToResume: [],
|
||||
|
||||
@@ -33,12 +33,18 @@ function isVideo (file) {
|
||||
function isAudio (file) {
|
||||
return [
|
||||
'.aac',
|
||||
'.aiff',
|
||||
'.ape',
|
||||
'.ac3',
|
||||
'.mp3',
|
||||
'.ogg',
|
||||
'.wav',
|
||||
'.flac',
|
||||
'.m4a'
|
||||
'.m4a',
|
||||
'.mp2',
|
||||
'.mp3',
|
||||
'.oga',
|
||||
'.ogg',
|
||||
'.opus',
|
||||
'.wav',
|
||||
'.wma'
|
||||
].includes(getFileExtension(file))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,39 +3,146 @@ module.exports = torrentPoster
|
||||
const captureFrame = require('capture-frame')
|
||||
const path = require('path')
|
||||
|
||||
const mediaExtensions = {
|
||||
audio: ['.aac', '.asf', '.flac', '.m2a', '.m4a', '.mp2', '.mp4', '.mp3', '.oga', '.ogg', '.opus',
|
||||
'.wma', '.wav', '.wv', '.wvp'],
|
||||
video: ['.mp4', '.m4v', '.webm', '.mov', '.mkv'],
|
||||
image: ['.gif', '.jpg', '.jpeg', '.png']
|
||||
}
|
||||
|
||||
function torrentPoster (torrent, cb) {
|
||||
// First, try to use a poster image if available
|
||||
const posterFile = torrent.files.filter(function (file) {
|
||||
return /^poster\.(jpg|png|gif)$/.test(file.name)
|
||||
})[0]
|
||||
if (posterFile) return torrentPosterFromImage(posterFile, torrent, cb)
|
||||
if (posterFile) return extractPoster(posterFile, cb)
|
||||
|
||||
// Second, try to use the largest video file
|
||||
// Filter out file formats that the <video> tag definitely can't play
|
||||
const videoFile = getLargestFileByExtension(torrent, ['.mp4', '.m4v', '.webm', '.mov', '.mkv'])
|
||||
if (videoFile) return torrentPosterFromVideo(videoFile, torrent, cb)
|
||||
// 'score' each media type based on total size present in torrent
|
||||
const bestScore = ['audio', 'video', 'image'].map(mediaType => {
|
||||
return {
|
||||
type: mediaType,
|
||||
size: calculateDataLengthByExtension(torrent, mediaExtensions[mediaType])}
|
||||
}).sort((a, b) => { // sort descending on size
|
||||
return b.size - a.size
|
||||
})[0]
|
||||
|
||||
// Third, try to use the largest image file
|
||||
const imgFile = getLargestFileByExtension(torrent, ['.gif', '.jpg', '.jpeg', '.png'])
|
||||
if (imgFile) return torrentPosterFromImage(imgFile, torrent, cb)
|
||||
if (bestScore.size === 0) {
|
||||
// Admit defeat, no video, audio or image had a significant presence
|
||||
return cb(new Error('Cannot generate a poster from any files in the torrent'))
|
||||
}
|
||||
|
||||
// TODO: generate a waveform from the largest sound file
|
||||
// Finally, admit defeat
|
||||
return cb(new Error('Cannot generate a poster from any files in the torrent'))
|
||||
// Based on which media type is dominant we select the corresponding poster function
|
||||
switch (bestScore.type) {
|
||||
case 'audio':
|
||||
return torrentPosterFromAudio(torrent, cb)
|
||||
case 'image':
|
||||
return torrentPosterFromImage(torrent, cb)
|
||||
case 'video':
|
||||
return torrentPosterFromVideo(torrent, cb)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the total data size of file matching one of the provided extensions
|
||||
* @param torrent
|
||||
* @param extensions List of extension to match
|
||||
* @returns {number} total size, of matches found (>= 0)
|
||||
*/
|
||||
function calculateDataLengthByExtension (torrent, extensions) {
|
||||
const files = filterOnExtension(torrent, extensions)
|
||||
if (files.length === 0) return 0
|
||||
return files
|
||||
.map(file => file.length)
|
||||
.reduce((a, b) => {
|
||||
return a + b
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the largest file of a given torrent, filtered by provided extension
|
||||
* @param torrent Torrent to search in
|
||||
* @param extensions Extension whitelist filter
|
||||
* @returns Torrent file object
|
||||
*/
|
||||
function getLargestFileByExtension (torrent, extensions) {
|
||||
const files = torrent.files.filter(function (file) {
|
||||
const extname = path.extname(file.name).toLowerCase()
|
||||
return extensions.indexOf(extname) !== -1
|
||||
})
|
||||
const files = filterOnExtension(torrent, extensions)
|
||||
if (files.length === 0) return undefined
|
||||
return files.reduce(function (a, b) {
|
||||
return files.reduce((a, b) => {
|
||||
return a.length > b.length ? a : b
|
||||
})
|
||||
}
|
||||
|
||||
function torrentPosterFromVideo (file, torrent, cb) {
|
||||
/**
|
||||
* Filter file on a list extension, can be used to find al image files
|
||||
* @param torrent Torrent to filter files from
|
||||
* @param extensions File extensions to filter on
|
||||
* @returns {number} Array of torrent file objects matching one of the given extensions
|
||||
*/
|
||||
function filterOnExtension (torrent, extensions) {
|
||||
return torrent.files.filter(file => {
|
||||
const extname = path.extname(file.name).toLowerCase()
|
||||
return extensions.indexOf(extname) !== -1
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a score how likely the file is suitable as a poster
|
||||
* @param imgFile File object of an image
|
||||
* @returns {number} Score, higher score is a better match
|
||||
*/
|
||||
function scoreAudioCoverFile (imgFile) {
|
||||
const fileName = path.basename(imgFile.name, path.extname(imgFile.name)).toLowerCase()
|
||||
const relevanceScore = {
|
||||
cover: 80,
|
||||
folder: 80,
|
||||
album: 80,
|
||||
front: 80,
|
||||
back: 20,
|
||||
spectrogram: -80
|
||||
}
|
||||
|
||||
for (let keyword in relevanceScore) {
|
||||
if (fileName === keyword) {
|
||||
return relevanceScore[keyword]
|
||||
}
|
||||
if (fileName.indexOf(keyword) !== -1) {
|
||||
return relevanceScore[keyword]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function torrentPosterFromAudio (torrent, cb) {
|
||||
const imageFiles = filterOnExtension(torrent, mediaExtensions.image)
|
||||
|
||||
const bestCover = imageFiles.map(file => {
|
||||
return {
|
||||
file: file,
|
||||
score: scoreAudioCoverFile(file)
|
||||
}
|
||||
}).reduce((a, b) => {
|
||||
if (a.score > b.score) {
|
||||
return a
|
||||
}
|
||||
if (b.score > a.score) {
|
||||
return b
|
||||
}
|
||||
// If score is equal, pick the largest file, aiming for highest resolution
|
||||
if (a.file.length > b.file.length) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
})
|
||||
|
||||
if (!bestCover) return cb(new Error('Generated poster contains no data'))
|
||||
|
||||
const extname = path.extname(bestCover.file.name)
|
||||
bestCover.file.getBuffer((err, buf) => cb(err, buf, extname))
|
||||
}
|
||||
|
||||
function torrentPosterFromVideo (torrent, cb) {
|
||||
const file = getLargestFileByExtension(torrent, mediaExtensions.video)
|
||||
|
||||
const index = torrent.files.indexOf(file)
|
||||
|
||||
const server = torrent.createServer(0)
|
||||
@@ -77,7 +184,12 @@ function torrentPosterFromVideo (file, torrent, cb) {
|
||||
}
|
||||
}
|
||||
|
||||
function torrentPosterFromImage (file, torrent, cb) {
|
||||
const extname = path.extname(file.name)
|
||||
file.getBuffer((err, buf) => cb(err, buf, extname))
|
||||
function torrentPosterFromImage (torrent, cb) {
|
||||
const file = getLargestFileByExtension(torrent, mediaExtensions.image)
|
||||
extractPoster(file, cb)
|
||||
}
|
||||
|
||||
function extractPoster (file, cb) {
|
||||
const extname = path.extname(file.name)
|
||||
file.getBuffer((err, buf) => { return cb(err, buf, extname) })
|
||||
}
|
||||
|
||||
@@ -459,6 +459,13 @@ function setDimensions (dimensions) {
|
||||
function onOpen (files) {
|
||||
if (!Array.isArray(files)) files = [ files ]
|
||||
|
||||
// File API seems to transform "magnet:?foo" in "magnet:///?foo"
|
||||
// this is a sanitization
|
||||
files = files.map(file => {
|
||||
if (typeof file !== 'string') return file
|
||||
return file.replace(/^magnet:\/+\?/i, 'magnet:?')
|
||||
})
|
||||
|
||||
const url = state.location.url()
|
||||
const allTorrents = files.every(TorrentPlayer.isTorrent)
|
||||
const allSubtitles = files.every(controllers.subtitles().isSubtitle)
|
||||
|
||||
@@ -160,6 +160,7 @@ function renderMedia (state) {
|
||||
} else {
|
||||
// When the last video completes, pause the video instead of looping
|
||||
state.playing.isPaused = true
|
||||
if (state.window.isFullScreen) dispatch('toggleFullScreen')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,25 +207,18 @@ function renderOverlay (state) {
|
||||
function renderAudioMetadata (state) {
|
||||
const fileSummary = state.getPlayingFileSummary()
|
||||
if (!fileSummary.audioInfo) return
|
||||
const info = fileSummary.audioInfo
|
||||
const common = fileSummary.audioInfo.common || {}
|
||||
|
||||
// Get audio track info
|
||||
let title = info.title
|
||||
if (!title) {
|
||||
title = fileSummary.name
|
||||
}
|
||||
let artist = info.artist && info.artist[0]
|
||||
let album = info.album
|
||||
if (album && info.year && !album.includes(info.year)) {
|
||||
album += ' (' + info.year + ')'
|
||||
}
|
||||
let track
|
||||
if (info.track && info.track.no && info.track.of) {
|
||||
track = info.track.no + ' of ' + info.track.of
|
||||
}
|
||||
const title = common.title ? common.title : fileSummary.name
|
||||
|
||||
// Show a small info box in the middle of the screen with title/album/etc
|
||||
const elems = []
|
||||
|
||||
// Audio metadata: artist(s)
|
||||
const artist = common.albumartist || common.artist ||
|
||||
(common.artists && common.artists.filter(function (a) { return a }).join(', ')) ||
|
||||
'(Unknown Artist)'
|
||||
if (artist) {
|
||||
elems.push((
|
||||
<div key='artist' className='audio-artist'>
|
||||
@@ -232,14 +226,52 @@ function renderAudioMetadata (state) {
|
||||
</div>
|
||||
))
|
||||
}
|
||||
if (album) {
|
||||
|
||||
// Audio metadata: album
|
||||
if (common.album) {
|
||||
elems.push((
|
||||
<div key='album' className='audio-album'>
|
||||
<label>Album</label>{album}
|
||||
<label>Album</label>{common.album}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
if (track) {
|
||||
|
||||
// Audio metadata: year
|
||||
if (common.year) {
|
||||
elems.push((
|
||||
<div key='year' className='audio-year'>
|
||||
<label>Year</label>{common.year}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
// Audio metadata: release information (label & catalog-number)
|
||||
if (common.label || common.catalognumber) {
|
||||
const releaseInfo = []
|
||||
if (common.label && common.catalognumber &&
|
||||
common.label.length === common.catalognumber.length) {
|
||||
// Assume labels & catalog-numbers are pairs
|
||||
for (let n = 0; n < common.label.length; ++n) {
|
||||
releaseInfo.push(common.label[0] + ' / ' + common.catalognumber[n])
|
||||
}
|
||||
} else {
|
||||
if (common.label) {
|
||||
releaseInfo.push(...common.label)
|
||||
}
|
||||
if (common.catalognumber) {
|
||||
releaseInfo.push(...common.catalognumber)
|
||||
}
|
||||
}
|
||||
elems.push((
|
||||
<div key='release' className='audio-release'>
|
||||
<label>Release</label>{ releaseInfo.join(', ') }
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
// Audio metadata: track-number
|
||||
if (common.track && common.track.no && common.track.of) {
|
||||
const track = common.track.no + ' of ' + common.track.of
|
||||
elems.push((
|
||||
<div key='track' className='audio-track'>
|
||||
<label>Track</label>{track}
|
||||
@@ -247,6 +279,38 @@ function renderAudioMetadata (state) {
|
||||
))
|
||||
}
|
||||
|
||||
// Audio metadata: format
|
||||
const format = []
|
||||
fileSummary.audioInfo.format = fileSummary.audioInfo.format || ''
|
||||
if (fileSummary.audioInfo.format.dataformat) {
|
||||
format.push(fileSummary.audioInfo.format.dataformat)
|
||||
}
|
||||
if (fileSummary.audioInfo.format.bitrate) {
|
||||
format.push(fileSummary.audioInfo.format.bitrate / 1000 + ' kbps')
|
||||
}
|
||||
if (fileSummary.audioInfo.format.sampleRate) {
|
||||
format.push(fileSummary.audioInfo.format.sampleRate / 1000 + ' kHz')
|
||||
}
|
||||
if (fileSummary.audioInfo.format.bitsPerSample) {
|
||||
format.push(fileSummary.audioInfo.format.bitsPerSample + ' bit')
|
||||
}
|
||||
if (format.length > 0) {
|
||||
elems.push((
|
||||
<div key='format' className='audio-format'>
|
||||
<label>Format</label>{ format.join(', ') }
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
// Audio metadata: comments
|
||||
if (common.comment) {
|
||||
elems.push((
|
||||
<div key='comments' className='audio-comments'>
|
||||
<label>Comments</label>{common.comment.join(' / ')}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
// Align the title with the other info, if available. Otherwise, center title
|
||||
const emptyLabel = (<label />)
|
||||
elems.unshift((
|
||||
@@ -273,7 +337,7 @@ function renderLoadingSpinner (state) {
|
||||
|
||||
return (
|
||||
<div key='loading' className='media-stalled'>
|
||||
<div key='loading-spinner' className='loading-spinner'> </div>
|
||||
<div key='loading-spinner' className='loading-spinner' />
|
||||
<div key='loading-progress' className='loading-status ellipsis'>
|
||||
<span className='progress'>{fileProgress}%</span> downloaded
|
||||
<span> ↓ {prettyBytes(prog.downloadSpeed || 0)}/s</span>
|
||||
@@ -303,7 +367,7 @@ function renderCastScreen (state) {
|
||||
isCast = false
|
||||
} else if (state.playing.location === 'error') {
|
||||
castIcon = 'error_outline'
|
||||
castType = 'Error'
|
||||
castType = 'Unable to Play'
|
||||
isCast = false
|
||||
}
|
||||
|
||||
@@ -497,9 +561,9 @@ function renderPlayerControls (state) {
|
||||
const volume = state.playing.volume
|
||||
const volumeIcon = 'volume_' + (
|
||||
volume === 0 ? 'off'
|
||||
: volume < 0.3 ? 'mute'
|
||||
: volume < 0.6 ? 'down'
|
||||
: 'up')
|
||||
: volume < 0.3 ? 'mute'
|
||||
: volume < 0.6 ? 'down'
|
||||
: 'up')
|
||||
const volumeStyle = {
|
||||
background: '-webkit-gradient(linear, left top, right top, ' +
|
||||
'color-stop(' + (volume * 100) + '%, #eee), ' +
|
||||
|
||||
@@ -216,7 +216,7 @@ module.exports = class TorrentList extends React.Component {
|
||||
} else { // torrentSummary.status is 'new' or something unexpected
|
||||
status = ''
|
||||
}
|
||||
return (<span>{status}</span>)
|
||||
return (<span key='torrent-status'>{status}</span>)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ const defaultAnnounceList = require('create-torrent').announceList
|
||||
const electron = require('electron')
|
||||
const fs = require('fs')
|
||||
const mkdirp = require('mkdirp')
|
||||
const musicmetadata = require('musicmetadata')
|
||||
const mm = require('music-metadata')
|
||||
const networkAddress = require('network-address')
|
||||
const path = require('path')
|
||||
const WebTorrent = require('webtorrent')
|
||||
@@ -98,7 +98,7 @@ function init () {
|
||||
|
||||
window.addEventListener('error', (e) =>
|
||||
ipc.send('wt-uncaught-error', {message: e.error.message, stack: e.error.stack}),
|
||||
true)
|
||||
true)
|
||||
|
||||
setInterval(updateTorrentProgress, 1000)
|
||||
console.timeEnd('init')
|
||||
@@ -334,16 +334,30 @@ function stopServer () {
|
||||
server = null
|
||||
}
|
||||
|
||||
console.log('Initializing...')
|
||||
|
||||
function getAudioMetadata (infoHash, index) {
|
||||
const torrent = client.get(infoHash)
|
||||
const file = torrent.files[index]
|
||||
musicmetadata(file.createReadStream(), function (err, info) {
|
||||
if (err) return console.log('error getting audio metadata for ' + infoHash + ':' + index, err)
|
||||
const { artist, album, albumartist, title, year, track, disk, genre } = info
|
||||
const importantInfo = { artist, album, albumartist, title, year, track, disk, genre }
|
||||
console.log('got audio metadata for %s: %o', file.name, importantInfo)
|
||||
ipc.send('wt-audio-metadata', infoHash, index, importantInfo)
|
||||
})
|
||||
|
||||
// Set initial matadata to display the filename first.
|
||||
const metadata = { title: file.name }
|
||||
ipc.send('wt-audio-metadata', infoHash, index, metadata)
|
||||
|
||||
const options = {native: false, skipCovers: true, fileSize: file.length}
|
||||
const onMetaData = file.done
|
||||
// If completed; use direct file access
|
||||
? mm.parseFile(path.join(torrent.path, file.path), options)
|
||||
// otherwise stream
|
||||
: mm.parseStream(file.createReadStream(), file.name, options)
|
||||
|
||||
onMetaData
|
||||
.then(function (metadata) {
|
||||
console.log('got audio metadata for %s (length=%s): %o', file.name, file.length, metadata)
|
||||
ipc.send('wt-audio-metadata', infoHash, index, metadata)
|
||||
}).catch(function (err) {
|
||||
return console.log('error getting audio metadata for ' + infoHash + ':' + index, err)
|
||||
})
|
||||
}
|
||||
|
||||
function selectFiles (torrentOrInfoHash, selections) {
|
||||
|
||||
Reference in New Issue
Block a user