Add repeat and shuffle options to the player
This commit is contained in:
@@ -28,6 +28,8 @@ function setPlayerOpen (flag) {
|
||||
getMenuItem('Play/Pause').enabled = flag
|
||||
getMenuItem('Skip Next').enabled = flag
|
||||
getMenuItem('Skip Previous').enabled = flag
|
||||
getMenuItem('Enable Shuffle').enabled = flag
|
||||
getMenuItem('Enable Repeat').enabled = flag
|
||||
getMenuItem('Increase Volume').enabled = flag
|
||||
getMenuItem('Decrease Volume').enabled = flag
|
||||
getMenuItem('Step Forward').enabled = flag
|
||||
@@ -39,12 +41,16 @@ function setPlayerOpen (flag) {
|
||||
if (flag === false) {
|
||||
getMenuItem('Skip Next').enabled = false
|
||||
getMenuItem('Skip Previous').enabled = false
|
||||
getMenuItem('Enable Shuffle').checked = false
|
||||
getMenuItem('Enable Repeat').checked = false
|
||||
}
|
||||
}
|
||||
|
||||
function onPlayerUpdate (hasNext, hasPrevious) {
|
||||
getMenuItem('Skip Next').enabled = hasNext
|
||||
getMenuItem('Skip Previous').enabled = hasPrevious
|
||||
function onPlayerUpdate (state) {
|
||||
getMenuItem('Skip Next').enabled = state.hasNext
|
||||
getMenuItem('Skip Previous').enabled = state.hasPrevious
|
||||
getMenuItem('Enable Shuffle').checked = state.shuffle
|
||||
getMenuItem('Enable Repeat').checked = state.repeat
|
||||
}
|
||||
|
||||
function setWindowFocus (flag) {
|
||||
@@ -215,6 +221,23 @@ function getMenuTemplate () {
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Enable Shuffle',
|
||||
type: 'checkbox',
|
||||
checked: false,
|
||||
click: () => windows.main.dispatch('toggleShuffle'),
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
label: 'Enable Repeat',
|
||||
type: 'checkbox',
|
||||
checked: false,
|
||||
click: () => windows.main.dispatch('toggleRepeat'),
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Increase Volume',
|
||||
accelerator: 'CmdOrCtrl+Up',
|
||||
|
||||
@@ -110,6 +110,24 @@ module.exports = class PlaybackController {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable or disable playlist shuffle
|
||||
toggleShuffle (flag) {
|
||||
var playlist = this.state.playlist
|
||||
if (playlist) {
|
||||
playlist.toggleShuffle(flag)
|
||||
ipcRenderer.send('onPlayerUpdate', playlist.getState())
|
||||
}
|
||||
}
|
||||
|
||||
// Enable or disable repetition of the entire playlist
|
||||
toggleRepeat (flag) {
|
||||
var playlist = this.state.playlist
|
||||
if (playlist) {
|
||||
playlist.toggleRepeat(flag)
|
||||
ipcRenderer.send('onPlayerUpdate', playlist.getState())
|
||||
}
|
||||
}
|
||||
|
||||
// Play (unpause) the current media
|
||||
play () {
|
||||
var state = this.state
|
||||
@@ -313,6 +331,8 @@ module.exports = class PlaybackController {
|
||||
|
||||
state.window.title = fileSummary.name
|
||||
|
||||
ipcRenderer.send('onPlayerUpdate', state.playlist.getState())
|
||||
|
||||
// play in VLC if set as default player (Preferences / Playback / Play in VLC)
|
||||
if (this.state.saved.prefs.openExternalPlayer) {
|
||||
dispatch('openExternalPlayer')
|
||||
@@ -324,7 +344,7 @@ module.exports = class PlaybackController {
|
||||
// otherwise, play the video
|
||||
this.update()
|
||||
|
||||
ipcRenderer.send('onPlayerUpdate', state.playlist.hasNext(), state.playlist.hasPrevious())
|
||||
ipcRenderer.send('onPlayerUpdate', state.playlist.getState())
|
||||
cb()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,16 @@ function Playlist (torrentSummary) {
|
||||
this._infoHash = torrentSummary.infoHash
|
||||
this._position = 0
|
||||
this._tracks = extractTracks(torrentSummary)
|
||||
this._order = range(0, this._tracks.length)
|
||||
|
||||
this._repeat = false
|
||||
this._shuffled = false
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Public methods
|
||||
// =============================================================================
|
||||
|
||||
Playlist.prototype.getInfoHash = function () {
|
||||
return this._infoHash
|
||||
}
|
||||
@@ -17,37 +25,61 @@ Playlist.prototype.getTracks = function () {
|
||||
}
|
||||
|
||||
Playlist.prototype.hasNext = function () {
|
||||
return this._position + 1 < this._tracks.length
|
||||
return !this._tracks.length ? false
|
||||
: this._repeat ? true
|
||||
: this._position + 1 < this._tracks.length
|
||||
}
|
||||
|
||||
Playlist.prototype.hasPrevious = function () {
|
||||
return this._position > 0
|
||||
return !this._tracks.length ? false
|
||||
: this._repeat ? true
|
||||
: this._position > 0
|
||||
}
|
||||
|
||||
Playlist.prototype.next = function () {
|
||||
if (this.hasNext()) {
|
||||
this._position++
|
||||
this._position = mod(this._position + 1, this._tracks.length)
|
||||
return this.getCurrent()
|
||||
}
|
||||
}
|
||||
|
||||
Playlist.prototype.previous = function () {
|
||||
if (this.hasPrevious()) {
|
||||
this._position--
|
||||
this._position = mod(this._position - 1, this._tracks.length)
|
||||
return this.getCurrent()
|
||||
}
|
||||
}
|
||||
|
||||
Playlist.prototype.shuffleEnabled = function () {
|
||||
return this._shuffled
|
||||
}
|
||||
|
||||
Playlist.prototype.toggleShuffle = function (value) {
|
||||
this._shuffled = (value === undefined ? !this._shuffled : value)
|
||||
this._shuffled ? this._shuffle() : this._unshuffle()
|
||||
}
|
||||
|
||||
Playlist.prototype.repeatEnabled = function () {
|
||||
return this._repeat
|
||||
}
|
||||
|
||||
Playlist.prototype.toggleRepeat = function (value) {
|
||||
this._repeat = (value === undefined ? !this._repeat : value)
|
||||
}
|
||||
|
||||
Playlist.prototype.jumpToFile = function (infoHash, fileIndex) {
|
||||
this.setPosition(this._tracks.findIndex(
|
||||
(track) => track.infoHash === infoHash && track.fileIndex === fileIndex
|
||||
))
|
||||
this.setPosition(this._order.findIndex((i) => {
|
||||
let track = this._tracks[i]
|
||||
return track.infoHash === infoHash && track.fileIndex === fileIndex
|
||||
}))
|
||||
return this.getCurrent()
|
||||
}
|
||||
|
||||
Playlist.prototype.getCurrent = function () {
|
||||
var position = this.getPosition()
|
||||
return position === undefined ? undefined : this._tracks[position]
|
||||
|
||||
return position === undefined ? undefined
|
||||
: this._tracks[this._order[position]]
|
||||
}
|
||||
|
||||
Playlist.prototype.getPosition = function () {
|
||||
@@ -60,6 +92,43 @@ Playlist.prototype.setPosition = function (position) {
|
||||
this._position = position
|
||||
}
|
||||
|
||||
Playlist.prototype.getState = function () {
|
||||
return {
|
||||
hasNext: this.hasNext(),
|
||||
hasPrevious: this.hasPrevious(),
|
||||
shuffle: this.shuffleEnabled(),
|
||||
repeat: this.repeatEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Private methods
|
||||
// =============================================================================
|
||||
|
||||
Playlist.prototype._shuffle = function () {
|
||||
let order = this._order
|
||||
if (!order.length) return
|
||||
|
||||
// Move the current track to the beggining of the playlist
|
||||
swap(order, 0, this._position)
|
||||
this._position = 0
|
||||
|
||||
// Shuffle the rest of the tracks with Fisher-Yates Shuffle
|
||||
for (let i = order.length - 1; i > 0; --i) {
|
||||
let j = Math.floor(Math.random() * i) + 1
|
||||
swap(order, i, j)
|
||||
}
|
||||
}
|
||||
|
||||
Playlist.prototype._unshuffle = function () {
|
||||
this._position = this._order[this._position]
|
||||
this._order = range(0, this._order.length)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Utility fuctions
|
||||
// =============================================================================
|
||||
|
||||
function extractTracks (torrentSummary) {
|
||||
return torrentSummary.files.map((file, index) => ({ file, index }))
|
||||
.filter((object) => TorrentPlayer.isPlayable(object.file))
|
||||
@@ -76,3 +145,17 @@ function extractTracks (torrentSummary) {
|
||||
: 'other'
|
||||
}))
|
||||
}
|
||||
|
||||
function range (begin, end) {
|
||||
return Array.apply(null, {length: end - begin}).map((v, i) => begin + i)
|
||||
}
|
||||
|
||||
function swap (array, i, j) {
|
||||
let temp = array[i]
|
||||
array[i] = array[j]
|
||||
array[j] = temp
|
||||
}
|
||||
|
||||
function mod (a, b) {
|
||||
return ((a % b) + b) % b
|
||||
}
|
||||
|
||||
@@ -190,6 +190,8 @@ const dispatchHandlers = {
|
||||
'playPause': () => controllers.playback.playPause(),
|
||||
'nextTrack': () => controllers.playback.nextTrack(),
|
||||
'previousTrack': () => controllers.playback.previousTrack(),
|
||||
'toggleShuffle': (flag) => controllers.playback.toggleShuffle(flag),
|
||||
'toggleRepeat': (flag) => controllers.playback.toggleRepeat(flag),
|
||||
'skip': (time) => controllers.playback.skip(time),
|
||||
'skipTo': (time) => controllers.playback.skipTo(time),
|
||||
'changePlaybackRate': (dir) => controllers.playback.changePlaybackRate(dir),
|
||||
|
||||
@@ -384,6 +384,8 @@ function renderPlayerControls (state) {
|
||||
: ''
|
||||
var prevClass = state.playlist.hasPrevious() ? '' : 'disabled'
|
||||
var nextClass = state.playlist.hasNext() ? '' : 'disabled'
|
||||
var repeatClass = state.playlist.repeatEnabled() ? 'active' : ''
|
||||
var shuffleClass = state.playlist.shuffleEnabled() ? 'active' : ''
|
||||
|
||||
var elements = [
|
||||
<div key='playback-bar' className='playback-bar'>
|
||||
@@ -444,6 +446,22 @@ function renderPlayerControls (state) {
|
||||
))
|
||||
}
|
||||
|
||||
elements.push(
|
||||
<i
|
||||
key='repeat'
|
||||
className={'icon repeat float-right ' + repeatClass}
|
||||
onClick={dispatcher('toggleRepeat')}>
|
||||
repeat
|
||||
</i>,
|
||||
|
||||
<i
|
||||
key='shuffle'
|
||||
className={'icon shuffle float-right ' + shuffleClass}
|
||||
onClick={dispatcher('toggleShuffle')}>
|
||||
shuffle
|
||||
</i>
|
||||
)
|
||||
|
||||
// If we've detected a Chromecast or AppleTV, the user can play video there
|
||||
var castTypes = ['chromecast', 'airplay', 'dlna']
|
||||
var isCastingAnywhere = castTypes.some(
|
||||
|
||||
@@ -743,6 +743,8 @@ body.drag .app::after {
|
||||
}
|
||||
|
||||
.player .controls .closed-caption.active,
|
||||
.player .controls .repeat.active,
|
||||
.player .controls .shuffle.active,
|
||||
.player .controls .device.active {
|
||||
color: #9af;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user