diff --git a/.gitignore b/.gitignore index eef72df..bc12b61 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ node_modules Destroyer-darwin-x64 Destroyer-win32-x64 Destroyer-linux-x64 +ffmpeg.zip +ffmpeg diff --git a/components/player/index.js b/components/player/index.js index 89c41da..27c154c 100644 --- a/components/player/index.js +++ b/components/player/index.js @@ -1,59 +1,58 @@ import { store } from '../../client.js' -const fs = require('fs') +import { remote } from 'electron' +const addSeconds = require('date-fns/add_seconds') +const differenceInSeconds = require('date-fns/difference_in_seconds') export default class Player { - constructor () { - this.ch1 = false - } playAlbum (album) { store.dispatch({type: 'PLAY_ALBUM', album: album}) this.local(store.getState().player.track) } playTrack (track) { store.dispatch({type: 'PLAY_TRACK', track: track}) - this.local(store.getState().player.track) + this.local(track) + } + next () { + if (store.getState().player.next) this.playTrack(store.getState().player.next) } stop () { - if (this.ch1.playing) this.ch1.stop() - if (this.ch1.asset) this.ch1.asset.stop() + remote.app.stop() store.dispatch({type: 'STOP'}) } toggle () { - if (this.ch1.playing) { - this.ch1.pause() - store.dispatch({type: 'PAUSE'}) - } else { - this.ch1.play() - store.dispatch({type: 'PLAY'}) - } + remote.app.toggle() + } + pause () { + clearInterval(this.interval) + store.dispatch({type: 'PAUSE'}) } - local (file) { - fs.readFile(file.path, (error, data) => { - if (error) throw error - this.stop() - store.dispatch({type: 'PLAY_TRACK', track: file}) - this.ch1 = AV.Player.fromBuffer(data) - this.ch1.play() - this.ch1.on('duration', (duration) => { - this.ch1.on('progress', (progress) => { - this.setCounter(Math.floor((duration - progress) / 1000)) - this.setPlaybar(duration, progress) - }) - }) - this.ch1.on('buffer', (buffer) => { - this.setBuffer(buffer) - }) - this.ch1.on('metadata', (metadata) => { - store.dispatch({type: 'METADATA', artist: metadata.artist, title: metadata.title, album: metadata.album}) - }) - this.ch1.on('error', (error) => { - store.dispatch({type: 'ERROR', error: error}) - }) - this.ch1.on('end', () => { - if (store.getState().player.next) this.local(store.getState().player.next) - else store.dispatch({type: 'STOP'}) - }) - }) + resume () { + this.setDuration(this.remaining, true) + store.dispatch({type: 'PLAY'}) + } + setDuration (duration = 0, previous) { + if (!previous) this.totalDuration = duration + let end = addSeconds(new Date(), duration) + this.seconds = differenceInSeconds(end, new Date()) + clearInterval(this.interval) + this.setCounter(this.seconds) + this.interval = setInterval(() => { + this.remaining = differenceInSeconds(end, new Date()) + if (this.remaining < 1) { + this.setCounter(0) + clearInterval(this.interval) + } else { + this.setCounter(this.remaining) + this.setPlaybar(this.totalDuration, this.remaining) + } + }, 666) + } + clearInterval () { + clearInterval(this.interval) + } + local (track) { + remote.app.playTrack(track) + store.dispatch({type: 'METADATA', artist: track.artist, title: track.title, album: track.album}) } setCounter (seconds) { if (!this.counter) this.counter = document.querySelector('figure h4') @@ -65,11 +64,7 @@ export default class Player { } setPlaybar (duration, progress) { if (!this.range) this.range = document.querySelector('[data-range]') - let elapsed = (progress / duration) * 100 - this.range.style.transform = 'translateX(' + (elapsed - 100) + '%)' - } - setBuffer (buffer) { - if (!this.buffer) this.buffer = document.querySelector('[data-buffer]') - this.buffer.style.transform = 'translateX(' + (buffer - 100) + '%)' + let elapsed = ((progress / duration) * 100) + this.range.style.width = `${100 - elapsed}%` } } diff --git a/components/player/reducer.js b/components/player/reducer.js index fce8a82..6c2ea57 100644 --- a/components/player/reducer.js +++ b/components/player/reducer.js @@ -1,5 +1,4 @@ import { store } from '../../client.js' -import Player from './index.js' export const playerReducer = (state = {}, action) => { switch (action.type) { @@ -26,6 +25,9 @@ export const playerReducer = (state = {}, action) => { state = {...state, track: action.track, next: next, previous: previous, albumIndex: albumIndex} break } + case 'STOP': { + state = {} + } } return state } diff --git a/ffbinaries.js b/ffbinaries.js new file mode 100644 index 0000000..d9e2325 --- /dev/null +++ b/ffbinaries.js @@ -0,0 +1,23 @@ +var ffbinaries = require('ffbinaries') +var platform = ffbinaries.detectPlatform() +var fs = require('fs') +var archiver = require('archiver') + +ffbinaries.downloadFiles(platform, {components: ['ffmpeg', 'ffplay', 'ffprobe'], destination: './ffmpeg'}, () => { + var output = fs.createWriteStream('ffmpeg.zip') + var archive = archiver('zip') + + output.on('close', function () { + console.log('ffmpeg complete') + }) + + archive.on('error', function (err) { + throw err + }) + + archive.pipe(output) + archive.bulk([ + {expand: true, cwd: './ffmpeg', src: ['**'], dest: '/'} + ]) + archive.finalize() +}) diff --git a/index.js b/index.js index 7f06589..3bcf209 100644 --- a/index.js +++ b/index.js @@ -2,11 +2,13 @@ const electron = require('electron') const app = electron.app const BrowserWindow = electron.BrowserWindow const windowStateKeeper = require('electron-window-state') -const ffbinaries = require('ffbinaries') -const platform = ffbinaries.detectPlatform() -const fs = require('fs') -const dest = app.getPath('userData') let mainWindow +let player +const spawn = require('child_process').spawn +const ffmpeg = require('fluent-ffmpeg') +const fs = require('fs') +const userData = app.getPath('userData') +var extract = require('extract-zip') const createWindow = () => { let mainWindowState = windowStateKeeper({ @@ -28,6 +30,7 @@ const createWindow = () => { mainWindowState.manage(mainWindow) mainWindow.loadURL('file://' + __dirname + '/components/app/index.html') mainWindow.on('closed', () => { + app.stop() mainWindow = null }) } @@ -36,6 +39,7 @@ app.on('ready', createWindow) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { + app.stop() app.quit() } }) @@ -46,39 +50,57 @@ app.on('activate', () => { } }) -if (!fs.existsSync(dest + '/hail-satan.txt')) { - ffbinaries.downloadFiles(platform, {components: ['ffmpeg'], quiet: true, destination: dest}, () => { - fs.writeFile(dest + '/hail-satan.txt', ` -Invoking the majestic throne of Satan... - ... - s, . .s - ss, . .. .ss - 'SsSs, .. . .sSsS' - sSs'sSs, . . .sSs'sSs - sSs 'sSs, ... .sSs' sSs - sS, 'sSs, .sSs' .Ss - 'Ss 'sSs, .sSs' sS' - ... sSs ' .sSs' sSs ... - . sSs .sSs' .., sSs . - . .. sS, .sSs' . 'sSs, .Ss . .. - .. . 'Ss .Ss' . 'sSs. '' .. . - . . sSs ' . 'sSs, . . - ... .sS.'sSs . .. 'sSs, ... - .sSs' sS, ..... .Ss 'sSs, - .sSs' 'Ss . sS' 'sSs, - .sSs' sSs . sSs 'sSs, - .sSs'____________________________ sSs ______________'sSs, -.sSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS'.Ss SSSSSSSSSSSSSSSSSSSSSs, - ... sS' - sSs sSs - sSs sSs - sS, .Ss - 'Ss sS' - sSs sSs - sSsSs - sSs - s -complete - `) +app.playTrack = file => { + app.stop() + player = spawn(`${userData}/ffmpeg/ffplay`, [file.path, '-nodisp', '-autoexit'], {stdio: 'ignore'}) + ffmpeg.setFfprobePath(`${userData}/ffmpeg/ffprobe`) + ffmpeg.ffprobe(file.path, (error, metadata) => { + let duration + if (error) throw error + else if (metadata.format) { + duration = metadata.format.duration + } else duration = false + mainWindow.webContents.executeJavaScript(`window.player.setDuration(${duration})`) + }) + player.on('exit', sss) + this.playing = this.cnt = true + this.track = file +} + +app.currentTrack = () => this.track + +const sss = () => { + if (this.cnt) next() + else app.stop() +} + +let next = () => mainWindow.webContents.executeJavaScript('window.player.next()') + +app.stop = () => { + if (player) player.removeListener('exit', sss) + if (this.playing) player.kill() + player = this.playing = this.cnt = this.track = null +} + +app.pause = () => { + this.playing = false + mainWindow.webContents.executeJavaScript('window.player.pause()') + player.kill('SIGSTOP') +} + +app.resume = () => { + this.playing = true + mainWindow.webContents.executeJavaScript('window.player.resume()') + player.kill('SIGCONT') +} + +app.toggle = () => { + if (this.playing) app.pause() + else app.resume() +} + +if (!fs.existsSync(`${userData}/ff.txt`)) { + extract(__dirname + '/ffmpeg.zip', {dir: userData}, function (err) { + fs.writeFile(`${userData}/ff.txt`, '🍑🍆') }) } diff --git a/package.json b/package.json index c7a5fde..927568c 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,15 @@ "build": "NODE_ENV=development webpack --progress", "build:watch": "npm run build -- --watch", "start": "electron index.js", + "ffmpeg": "node ./ffbinaries.js", "pack:osx": "rm -rf Destroyer-darwin-x64 && NODE_ENV=production webpack --progress -p && electron-packager ./ Destroyer --platform=darwin --arch=x64 --icon=icons.icns --prune" }, "author": "omar mashaal", "license": "ISC", "dependencies": { + "date-fns": "^1.23.0", "electron-window-state": "^4.0.1", - "ffbinaries": "^0.1.2", + "extract-zip": "^1.6.0", "fluent-ffmpeg": "2.0.1", "fuzzy-search": "^1.4.0", "in-view": "^0.6.1", @@ -32,6 +34,7 @@ "walk": "^2.3.9" }, "devDependencies": { + "archiver": "^1.3.0", "babel-core": "^6.21.0", "babel-eslint": "^7.1.1", "babel-loader": "^6.2.10", @@ -50,6 +53,7 @@ "eslint-plugin-react": "^6.8.0", "eslint-plugin-standard": "^2.0.1", "exports-loader": "^0.6.3", + "ffbinaries": "^0.1.2", "file-loader": "^0.9.0", "imports-loader": "^0.7.0", "url-loader": "^0.5.7",