Skip to content

Commit

Permalink
Add repeat and shuffle options to the player
Browse files Browse the repository at this point in the history
  • Loading branch information
Goldob committed Sep 1, 2016
1 parent b8f6f9f commit 9284122
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 12 deletions.
29 changes: 26 additions & 3 deletions src/main/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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',
Expand Down
22 changes: 21 additions & 1 deletion src/renderer/controllers/playback-controller.js
Original file line number Diff line number Diff line change
Expand 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
Expand Down Expand Up @@ -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')
Expand All @@ -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()
}

Expand Down
99 changes: 91 additions & 8 deletions src/renderer/lib/playlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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 () {
Expand All @@ -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))
Expand All @@ -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
}
2 changes: 2 additions & 0 deletions src/renderer/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
18 changes: 18 additions & 0 deletions src/renderer/pages/PlayerPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'>
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions static/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 9284122

Please sign in to comment.