diff --git a/index.html b/index.html index 6e42114..47e3ae9 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,13 @@ - React Native Tutorial + Tutorial Player - + + - +
@@ -31,9 +32,10 @@
+ + - - + \ No newline at end of file diff --git a/main-process/communication/async-msg.js b/main-process/communication/async-msg.js deleted file mode 100644 index 50b2b3e..0000000 --- a/main-process/communication/async-msg.js +++ /dev/null @@ -1,5 +0,0 @@ -const {ipcMain} = require('electron') - -ipcMain.on('asynchronous-message', (event, arg) => { - event.sender.send('asynchronous-reply', 'pong') -}) diff --git a/main-process/communication/sync-msg.js b/main-process/communication/sync-msg.js deleted file mode 100644 index fcdd680..0000000 --- a/main-process/communication/sync-msg.js +++ /dev/null @@ -1,5 +0,0 @@ -const {ipcMain} = require('electron') - -ipcMain.on('synchronous-message', (event, arg) => { - event.returnValue = 'pong' -}) diff --git a/main-process/drop_dir.js b/main-process/drop_dir.js new file mode 100644 index 0000000..6afd9c0 --- /dev/null +++ b/main-process/drop_dir.js @@ -0,0 +1,37 @@ +const { ipcMain } = require("electron"); +const fs = require("fs"); +const path = require("path"); + +const createTutorialList = require("../utils/create_tutorial_list"); + +ipcMain.on("Request", (event, dirPath) => { + if (!fs.existsSync(dirPath)) { + event.sender.send("Response", { + code: 400, + reason: "NOEXITS", + content: "The file/directory does not exist" + }); + } else if (!fs.lstatSync(dirPath).isDirectory()) { + event.sender.send("Response", { + code: 401, + reason: "NOTDIR", + content: "The file is not a directory" + }); + } else if( path.basename(dirPath).match(/[^a-zA-Z0-9_\-\.\'\"\& ]+/g) !== null ) { + event.sender.send("Response", { + code: 300, + reason: "INVALID_DIRNAME", + content: "The directory name is invalid", + data: path.basename(dirPath) + }); + } else { + createTutorialList(dirPath, (data) => { + event.sender.send("Response", { + code: 201, + reason: "CREATED", + content: "The list is created successfully!", + data: data + }); + }); + } +}); diff --git a/main-process/main-process.rar b/main-process/main-process.rar new file mode 100644 index 0000000..37330c7 Binary files /dev/null and b/main-process/main-process.rar differ diff --git a/main-process/menus/application-menu.js b/main-process/menus/application-menu.js deleted file mode 100644 index b543889..0000000 --- a/main-process/menus/application-menu.js +++ /dev/null @@ -1,240 +0,0 @@ -const {BrowserWindow, Menu, app, shell, dialog} = require('electron') - -let template = [{ - label: 'Edit', - submenu: [{ - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - role: 'undo' - }, { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - role: 'redo' - }, { - type: 'separator' - }, { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - role: 'cut' - }, { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - role: 'copy' - }, { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - role: 'paste' - }, { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - role: 'selectall' - }] -}, { - label: 'View', - submenu: [{ - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: (item, focusedWindow) => { - if (focusedWindow) { - // on reload, start fresh and close any old - // open secondary windows - if (focusedWindow.id === 1) { - BrowserWindow.getAllWindows().forEach(win => { - if (win.id > 1) win.close() - }) - } - focusedWindow.reload() - } - } - }, { - label: 'Toggle Full Screen', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Ctrl+Command+F' - } else { - return 'F11' - } - })(), - click: (item, focusedWindow) => { - if (focusedWindow) { - focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) - } - } - }, { - label: 'Toggle Developer Tools', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Alt+Command+I' - } else { - return 'Ctrl+Shift+I' - } - })(), - click: (item, focusedWindow) => { - if (focusedWindow) { - focusedWindow.toggleDevTools() - } - } - }, { - type: 'separator' - }, { - label: 'App Menu Demo', - click: function (item, focusedWindow) { - if (focusedWindow) { - const options = { - type: 'info', - title: 'Application Menu Demo', - buttons: ['Ok'], - message: 'This demo is for the Menu section, showing how to create a clickable menu item in the application menu.' - } - dialog.showMessageBox(focusedWindow, options, function () {}) - } - } - }] -}, { - label: 'Window', - role: 'window', - submenu: [{ - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, { - type: 'separator' - }, { - label: 'Reopen Window', - accelerator: 'CmdOrCtrl+Shift+T', - enabled: false, - key: 'reopenMenuItem', - click: () => { - app.emit('activate') - } - }] -}, { - label: 'Help', - role: 'help', - submenu: [{ - label: 'Learn More', - click: () => { - shell.openExternal('http://electron.atom.io') - } - }] -}] - -function addUpdateMenuItems (items, position) { - if (process.mas) return - - const version = app.getVersion() - let updateItems = [{ - label: `Version ${version}`, - enabled: false - }, { - label: 'Checking for Update', - enabled: false, - key: 'checkingForUpdate' - }, { - label: 'Check for Update', - visible: false, - key: 'checkForUpdate', - click: () => { - require('electron').autoUpdater.checkForUpdates() - } - }, { - label: 'Restart and Install Update', - enabled: true, - visible: false, - key: 'restartToUpdate', - click: () => { - require('electron').autoUpdater.quitAndInstall() - } - }] - - items.splice.apply(items, [position, 0].concat(updateItems)) -} - -function findReopenMenuItem () { - const menu = Menu.getApplicationMenu() - if (!menu) return - - let reopenMenuItem - menu.items.forEach(item => { - if (item.submenu) { - item.submenu.items.forEach(item => { - if (item.key === 'reopenMenuItem') { - reopenMenuItem = item - } - }) - } - }) - return reopenMenuItem -} - -if (process.platform === 'darwin') { - const name = app.getName() - template.unshift({ - label: name, - submenu: [{ - label: `About ${name}`, - role: 'about' - }, { - type: 'separator' - }, { - label: 'Services', - role: 'services', - submenu: [] - }, { - type: 'separator' - }, { - label: `Hide ${name}`, - accelerator: 'Command+H', - role: 'hide' - }, { - label: 'Hide Others', - accelerator: 'Command+Alt+H', - role: 'hideothers' - }, { - label: 'Show All', - role: 'unhide' - }, { - type: 'separator' - }, { - label: 'Quit', - accelerator: 'Command+Q', - click: () => { - app.quit() - } - }] - }) - - // Window menu. - template[3].submenu.push({ - type: 'separator' - }, { - label: 'Bring All to Front', - role: 'front' - }) - - addUpdateMenuItems(template[0].submenu, 1) -} - -if (process.platform === 'win32') { - const helpMenu = template[template.length - 1].submenu - addUpdateMenuItems(helpMenu, 0) -} - -app.on('ready', () => { - const menu = Menu.buildFromTemplate(template) - Menu.setApplicationMenu(menu) -}) - -app.on('browser-window-created', () => { - let reopenMenuItem = findReopenMenuItem() - if (reopenMenuItem) reopenMenuItem.enabled = false -}) - -app.on('window-all-closed', () => { - let reopenMenuItem = findReopenMenuItem() - if (reopenMenuItem) reopenMenuItem.enabled = true -}) diff --git a/main-process/menus/context-menu.js b/main-process/menus/context-menu.js deleted file mode 100644 index ebb321c..0000000 --- a/main-process/menus/context-menu.js +++ /dev/null @@ -1,23 +0,0 @@ -const { - BrowserWindow, - Menu, - MenuItem, - ipcMain, - app -} = require('electron') - -const menu = new Menu() -menu.append(new MenuItem({ label: 'Hello' })) -menu.append(new MenuItem({ type: 'separator' })) -menu.append(new MenuItem({ label: 'Electron', type: 'checkbox', checked: true })) - -app.on('browser-window-created', (event, win) => { - win.webContents.on('context-menu', (e, params) => { - menu.popup(win, params.x, params.y) - }) -}) - -ipcMain.on('show-context-menu', (event) => { - const win = BrowserWindow.fromWebContents(event.sender) - menu.popup(win) -}) diff --git a/main-process/menus/shortcuts.js b/main-process/menus/shortcuts.js deleted file mode 100644 index 656da46..0000000 --- a/main-process/menus/shortcuts.js +++ /dev/null @@ -1,16 +0,0 @@ -const {app, dialog, globalShortcut} = require('electron') - -app.on('ready', () => { - globalShortcut.register('CommandOrControl+Alt+K', () => { - dialog.showMessageBox({ - type: 'info', - message: 'Success!', - detail: 'You pressed the registered global shortcut keybinding.', - buttons: ['OK'] - }) - }) -}) - -app.on('will-quit', () => { - globalShortcut.unregisterAll() -}) diff --git a/main-process/native-ui/dialogs/error.js b/main-process/native-ui/dialogs/error.js deleted file mode 100644 index 4ed0843..0000000 --- a/main-process/native-ui/dialogs/error.js +++ /dev/null @@ -1,5 +0,0 @@ -const {ipcMain, dialog} = require('electron') - -ipcMain.on('open-error-dialog', (event) => { - dialog.showErrorBox('An Error Message', 'Demonstrating an error message.') -}) diff --git a/main-process/native-ui/dialogs/information.js b/main-process/native-ui/dialogs/information.js deleted file mode 100644 index 5054a1d..0000000 --- a/main-process/native-ui/dialogs/information.js +++ /dev/null @@ -1,13 +0,0 @@ -const {ipcMain, dialog} = require('electron') - -ipcMain.on('open-information-dialog', (event) => { - const options = { - type: 'info', - title: 'Information', - message: "This is an information dialog. Isn't it nice?", - buttons: ['Yes', 'No'] - } - dialog.showMessageBox(options, (index) => { - event.sender.send('information-dialog-selection', index) - }) -}) diff --git a/main-process/native-ui/dialogs/open-file.js b/main-process/native-ui/dialogs/open-file.js deleted file mode 100644 index 1aaf375..0000000 --- a/main-process/native-ui/dialogs/open-file.js +++ /dev/null @@ -1,11 +0,0 @@ -const {ipcMain, dialog} = require('electron') - -ipcMain.on('open-file-dialog', (event) => { - dialog.showOpenDialog({ - properties: ['openFile', 'openDirectory'] - }, (files) => { - if (files) { - event.sender.send('selected-directory', files) - } - }) -}) diff --git a/main-process/native-ui/dialogs/save.js b/main-process/native-ui/dialogs/save.js deleted file mode 100644 index b10ed3f..0000000 --- a/main-process/native-ui/dialogs/save.js +++ /dev/null @@ -1,13 +0,0 @@ -const {ipcMain, dialog} = require('electron') - -ipcMain.on('save-dialog', (event) => { - const options = { - title: 'Save an Image', - filters: [ - { name: 'Images', extensions: ['jpg', 'png', 'gif'] } - ] - } - dialog.showSaveDialog(options, (filename) => { - event.sender.send('saved-file', filename) - }) -}) diff --git a/main-process/native-ui/drag/codeIcon.png b/main-process/native-ui/drag/codeIcon.png deleted file mode 100644 index 2dd68b6..0000000 Binary files a/main-process/native-ui/drag/codeIcon.png and /dev/null differ diff --git a/main-process/native-ui/drag/drag.js b/main-process/native-ui/drag/drag.js deleted file mode 100644 index 1e46f0c..0000000 --- a/main-process/native-ui/drag/drag.js +++ /dev/null @@ -1,10 +0,0 @@ -const {ipcMain} = require('electron') -const path = require('path') - -ipcMain.on('ondragstart', (event, filepath) => { - const iconName = 'codeIcon.png' - event.sender.startDrag({ - file: filepath, - icon: path.join(__dirname, iconName) - }) -}) diff --git a/main-process/native-ui/tray/iconTemplate.png b/main-process/native-ui/tray/iconTemplate.png deleted file mode 100644 index d56c508..0000000 Binary files a/main-process/native-ui/tray/iconTemplate.png and /dev/null differ diff --git a/main-process/native-ui/tray/iconTemplate@2x.png b/main-process/native-ui/tray/iconTemplate@2x.png deleted file mode 100644 index bd30644..0000000 Binary files a/main-process/native-ui/tray/iconTemplate@2x.png and /dev/null differ diff --git a/main-process/native-ui/tray/tray.js b/main-process/native-ui/tray/tray.js deleted file mode 100644 index f43dff2..0000000 --- a/main-process/native-ui/tray/tray.js +++ /dev/null @@ -1,28 +0,0 @@ -const path = require('path') -const {ipcMain, app, Menu, Tray} = require('electron') - -let appIcon = null - -ipcMain.on('put-in-tray', (event) => { - const iconName = process.platform === 'win32' ? 'windows-icon.png' : 'iconTemplate.png' - const iconPath = path.join(__dirname, iconName) - appIcon = new Tray(iconPath) - - const contextMenu = Menu.buildFromTemplate([{ - label: 'Remove', - click: () => { - event.sender.send('tray-removed') - } - }]) - - appIcon.setToolTip('Electron Demo in the tray.') - appIcon.setContextMenu(contextMenu) -}) - -ipcMain.on('remove-tray', () => { - appIcon.destroy() -}) - -app.on('window-all-closed', () => { - if (appIcon) appIcon.destroy() -}) diff --git a/main-process/native-ui/tray/windows-icon@2x.png b/main-process/native-ui/tray/windows-icon@2x.png deleted file mode 100644 index 463f374..0000000 Binary files a/main-process/native-ui/tray/windows-icon@2x.png and /dev/null differ diff --git a/main-process/system/app-information.js b/main-process/system/app-information.js deleted file mode 100644 index 6b3290d..0000000 --- a/main-process/system/app-information.js +++ /dev/null @@ -1,5 +0,0 @@ -const {app, ipcMain} = require('electron') - -ipcMain.on('get-app-path', (event) => { - event.sender.send('got-app-path', app.getAppPath()) -}) diff --git a/main-process/system/protocol-handler.js b/main-process/system/protocol-handler.js deleted file mode 100644 index c290e1e..0000000 --- a/main-process/system/protocol-handler.js +++ /dev/null @@ -1,14 +0,0 @@ -const {app, dialog} = require('electron') -const path = require('path') - -if (process.defaultApp) { - if (process.argv.length >= 2) { - app.setAsDefaultProtocolClient('electron-api-demos', process.execPath, [path.resolve(process.argv[1])]) - } -} else { - app.setAsDefaultProtocolClient('electron-api-demos') -} - -app.on('open-url', (event, url) => { - dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`) -}) diff --git a/player/components/bootstrap.js b/player/components/bootstrap.js new file mode 100644 index 0000000..44e15d4 --- /dev/null +++ b/player/components/bootstrap.js @@ -0,0 +1,85 @@ +class BootStrap { + onReady = callback => { + if (typeof callback === "function") { + var onceCalled = false; + document.addEventListener("DOMContentLoaded", function(event) { + if (!onceCalled) { + onceCalled = true; + callback(event); + } + }); + document.onreadystatechange = function(event) { + if (document.readyState == "interactive") { + if (!onceCalled) { + onceCalled = true; + callback(event); + } + } + }; + } + }; + + loadComponents = async event => { + const { ipcRenderer } = require("electron"); + let selectDir = ""; + document.addEventListener("dragover", e => { + e.preventDefault(); + }); + document.addEventListener("drop", e => { + e.preventDefault(); + if (e.dataTransfer.files.length === 1) { + selectDir = e.dataTransfer.files[0].path; + ipcRenderer.send("Request", selectDir); + } else { + alert("You can drop only 1 directory at once."); + } + return false; + }); + + ipcRenderer.on("Response", (event, response) => { + if (response.code !== 201) { + alert(response.content); + } else { + if (selectDir != "") { + Helper.setConf("list_dir", selectDir); + Helper.setConf("currentTime", 0); + Helper.setConf("playedIndex", 0); + } + alert(response.content); + location.reload(); + } + }); + + const tutorialList = new TutorialList(); + const player = new Player(); + + await tutorialList.load(); + + tutorialList.bindEvents("change", e => { + Helper.setConf("currentTime", 0); + player.play(tutorialList.selectedValue()); + Helper.setConf("playedIndex", tutorialList.getIndex()); + }); + + player.bindEvents("onended", args => { + Helper.setConf("currentTime", 0); + let nextIndex = tutorialList.getIndex() + 1; + if (nextIndex < tutorialList.length()) { + tutorialList.setIndex(nextIndex); + player.play(tutorialList.selectedValue(), true); + } + }); + + let playedIndex; + if ((playedIndex = Helper.getConf("playedIndex"))) { + tutorialList.setIndex(parseInt(playedIndex)); + player.play(tutorialList.selectedValue(), false); + } + }; + + initialize = () => { + this.onReady(this.loadComponents); + }; +} + +module.exports = new BootStrap(); diff --git a/player/components/helper.js b/player/components/helper.js new file mode 100644 index 0000000..f61fcac --- /dev/null +++ b/player/components/helper.js @@ -0,0 +1,37 @@ +module.exports = class Helper { + static validateTitle = title => { + var ret = title; + ret = ret.replace(/\.vtt/gi, ""); + return ret.replace(/\-/gi, " "); + }; + + static loadScript = async path => { + let script = document.createElement("script"); + script.src = path; + document.getElementsByTagName("head")[0].appendChild(script); + return new Promise((resolve, eject) => { + script.onload = () => resolve(); + script.onerror = () => eject(); + }); + }; + + static getListDir = () => { + const cached_list_dir = Helper.getConf("list_dir"); + return cached_list_dir; + }; + + static getHumanTitle = path => { + let ret = path.replace(/[\\\/]/g, " > "); + ret = ret.replace(/\.mp4/g, ""); + ret = ret.replace(/\.vtt/g, ""); + return ret.replace(/[^A-Za-z0-9\.\>]/g, " "); + }; + + static getConf = key => { + return localStorage.getItem(key); + }; + + static setConf = (key, value) => { + localStorage.setItem(key, value); + }; +}; diff --git a/player/components/player.js b/player/components/player.js new file mode 100644 index 0000000..b40614d --- /dev/null +++ b/player/components/player.js @@ -0,0 +1,55 @@ +module.exports = class Player { + constructor() { + this.playerUI = document.getElementById("video_player"); + this.captionUI = document.getElementById("playerCaption"); + this.titleUI = document.getElementById("video_title"); + + return; + new MediaElementPlayer(this.playerUI, { + stretching: "auto", + pluginPath: "player/media-elements/build/", + success: function(media) { + var renderer = document.getElementById(media.id + "-rendername"); + + media.addEventListener("loadedmetadata", function() {}); + + media.addEventListener("error", function(e) {}); + } + }); + } + + bindEvents = (evName, callback) => { + this.playerUI[evName] = callback; + }; + + play = (index, auto_play = true) => { + if (index === 0) { + return; + } + var base_dir = Helper.getListDir(); + this.playerUI.preload = true; + this.playerUI.src = base_dir + "/" + mp4_files[index]; + this.captionUI.src = base_dir + "/" + vtt_files[index]; + this.playerUI.textTracks[0].mode = "showing"; + this.titleUI.innerText = Helper.getHumanTitle(mp4_files[index]); + + this.playerUI.ontimeupdate = () => { + if (this.playerUI.currentTime > 0) { + Helper.setConf("currentTime", this.playerUI.currentTime); + } + }; + + this.playerUI.focus(); + + if (auto_play) { + this.playerUI.play(); + } else { + setTimeout(() => { + var currentTime; + if ((currentTime = Helper.getConf("currentTime"))) { + this.playerUI.currentTime = parseFloat(currentTime); + } + }, 1000); + } + }; +}; diff --git a/player/components/seekbar.js b/player/components/seekbar.js new file mode 100644 index 0000000..b67a4e3 --- /dev/null +++ b/player/components/seekbar.js @@ -0,0 +1,36 @@ +module.exports = class Seekbar { + constructor() { + this.UI = document.getElementById("seekbar"); + this.posUI = document.getElementById("seekbar_pos"); + this.UI.ontimeupdate = function() { + var percentage = (vid.currentTime / vid.duration) * 100; + $("#custom-seekbar span").css("width", percentage + "%"); + }; + + this.drag_started = false; + + this.posUI.onmousedown = e => { + console.log("onmousedown..."); + this.drag_started = true; + }; + this.UI.onmousemove = e => { + if (this.drag_started) { + console.log("onmousemove..."); + this.posUI.style.left = e.pageX; + } + }; + this.UI.onmouseup = e => { + console.log("onmouseup..."); + this.drag_started = false; + }; + this.posUI.onmouseup = e => { + console.log("onmouseup..."); + this.drag_started = false; + }; + } + + bindEvents = (evName, callback) => { + this.UI.addEventListener(evName, callback); + this.UI[evName] = callback; + }; +}; diff --git a/player/components/tutorial_list.js b/player/components/tutorial_list.js new file mode 100644 index 0000000..9e4fca6 --- /dev/null +++ b/player/components/tutorial_list.js @@ -0,0 +1,99 @@ +module.exports = class TutorialList { + constructor() { + this.UI = document.getElementById("tutorialList"); + this.selectorButton = document.getElementById("selectListBtn"); + this.selectorButton.addEventListener("click", e => { + this.setDir(); + }); + } + + bindEvents = (evName, callback) => { + this.UI.addEventListener(evName, callback); + }; + + load = async () => { + var cached_list_dir = Helper.getListDir(); + if (cached_list_dir) { + await Helper.loadScript(cached_list_dir + "/list_mp4.js"); + await Helper.loadScript(cached_list_dir + "/list_vtt.js"); + + await this.loadOptionTags(); + } + return new Promise((resolve, eject) => { + resolve(); + }); + }; + + setIndex = index => { + this.UI.selectedIndex = index; + }; + + getIndex = () => this.UI.selectedIndex; + length = () => this.UI.options.length; + + selectedValue = () => { + if (this.UI.options[this.UI.selectedIndex]) { + return this.UI.options[this.UI.selectedIndex].value; + } else { + return 0; + } + }; + + setDir = function() { + // const win = require('electron').remote.getCurrentWindow(); + // win.setFullScreen(true); + // return; + const app = require("electron").remote.app; + var basepath = Helper.getListDir() || app.getAppPath(); + const dialog = require("electron").remote.dialog; + const list_dir = dialog.showOpenDialog(null, { + properties: ["openDirectory"], + defaultPath: basepath + }); + if (list_dir) { + Helper.setConf("list_dir", list_dir); + } + load(); + }; + + clearHtml = function() { + this.UI.innerHTML = ""; + }; + + loadOptionTags = () => { + this.clearHtml(); + + let temp_vtt_file_dir = ""; + let optionNode; + + optionNode = document.createElement("option"); + optionNode.value = ""; + optionNode.innerHTML = "Select a video to play"; + this.UI.appendChild(optionNode); + + for (var k in vtt_files) { + var vtt_file = vtt_files[k]; + var vtt_file_dir = vtt_file.split(/\//gi)[0]; + + if (vtt_file.indexOf("/") > -1 && vtt_file_dir != temp_vtt_file_dir) { + optionNode = document.createElement("optgroup"); + optionNode.label = Helper.validateTitle(vtt_file_dir); + this.UI.appendChild(optionNode); + } + + optionNode = document.createElement("option"); + optionNode.value = k; + if (vtt_file.indexOf("/") > -1){ + optionNode.innerHTML = Helper.validateTitle( + vtt_file.replace(vtt_file_dir + "/", "   ") + ); + } else { + optionNode.innerHTML = vtt_file; + } + this.UI.appendChild(optionNode); + temp_vtt_file_dir = vtt_file_dir; + } + + this.UI.appendChild(optionNode); + }; +}; diff --git a/player/custom.js b/player/custom.js deleted file mode 100644 index 3b3b466..0000000 --- a/player/custom.js +++ /dev/null @@ -1,287 +0,0 @@ -document.ready = (callback) => { - if (typeof callback === 'function') { - var onceCalled = false - document.addEventListener("DOMContentLoaded", function (event) { - if (!onceCalled) { - onceCalled = true - callback(event) - } - }) - document.onreadystatechange = function (event) { - if (document.readyState == "interactive") { - if (!onceCalled) { - onceCalled = true - callback(event) - } - } - } - } -} - -class Helper { - static validateTitle = (title) => { - var ret = title - ret = ret.replace(/\.vtt/gi, '') - return ret.replace(/\-/gi, ' ') - } - - static loadScript = async (path) => { - let script = document.createElement('script') - script.src = path - document.getElementsByTagName('head')[0].appendChild(script) - return new Promise((resolve, eject) => { - script.onload = () => resolve() - script.onerror = () => eject() - }) - } - - static getListDir = () => { - const cached_list_dir = Helper.getConf('list_dir') - return cached_list_dir - } - - static getHumanTitle = (path) => { - let ret = path.replace(/[\\\/]/g, ' > ') - ret = ret.replace(/\.mp4/g, '') - ret = ret.replace(/\.vtt/g, '') - return ret.replace(/[^A-Za-z0-9\.\>]/g, ' ') - } - - static getConf = (key) => { - return localStorage.getItem(key) - } - - static setConf = (key, value) => { - localStorage.setItem(key, value) - } - -} - - -class Player { - - constructor() { - this.playerUI = document.getElementById('video_player') - this.captionUI = document.getElementById('playerCaption') - this.titleUI = document.getElementById('video_title') - - return; - new MediaElementPlayer(this.playerUI, { - stretching: 'auto', - pluginPath: 'player/media-elements/build/', - success: function (media) { - var renderer = document.getElementById(media.id + '-rendername'); - - media.addEventListener('loadedmetadata', function () { - - }); - - media.addEventListener('error', function (e) { - - }); - } - }); - - } - - bindEvents = (evName, callback) => { - this.playerUI[evName] = callback - } - - play = (index, auto_play = true) => { - if (index === 0) { - return - } - var base_dir = Helper.getListDir() - this.playerUI.preload = true - this.playerUI.src = base_dir + '/' + mp4_files[index] - this.captionUI.src = base_dir + '/' + vtt_files[index] - this.playerUI.textTracks[0].mode = 'showing' - this.titleUI.innerText = Helper.getHumanTitle(mp4_files[index]) - - this.playerUI.ontimeupdate = () => { - if (this.playerUI.currentTime > 0) { - Helper.setConf('currentTime', this.playerUI.currentTime) - } - } - - this.playerUI.focus() - - if (auto_play) { - this.playerUI.play() - } else { - setTimeout(() => { - var currentTime - if (currentTime = Helper.getConf('currentTime')) { - this.playerUI.currentTime = parseFloat(currentTime) - } - }, 1000) - } - } -} - - -class TutorialList { - - constructor() { - this.UI = document.getElementById('tutorialList') - this.selectorButton = document.getElementById('selectListBtn') - this.selectorButton.addEventListener('click', (e) => { - this.setDir() - }) - } - - bindEvents = (evName, callback) => { - this.UI.addEventListener(evName, callback) - } - - load = async () => { - var cached_list_dir = Helper.getListDir() - if (cached_list_dir) { - await Helper.loadScript(cached_list_dir + '/list_mp4.js') - await Helper.loadScript(cached_list_dir + '/list_vtt.js') - - await this.loadOptionTags() - } - return new Promise((resolve, eject) => { resolve() }) - } - - setIndex = (index) => { - this.UI.selectedIndex = index - } - - getIndex = () => this.UI.selectedIndex - length = () => this.UI.options.length - - selectedValue = () => { - if (this.UI.options[this.UI.selectedIndex]) { - return this.UI.options[this.UI.selectedIndex].value - } else { - return 0 - } - } - - setDir = function () { - // const win = require('electron').remote.getCurrentWindow(); - // win.setFullScreen(true); - // return; - const app = require('electron').remote.app - var basepath = Helper.getListDir() || app.getAppPath() - const dialog = require('electron').remote.dialog - const list_dir = dialog.showOpenDialog(null, { - properties: ['openDirectory'], - defaultPath: basepath - }) - if (list_dir) { - Helper.setConf('list_dir', list_dir) - } - load() - } - - clearHtml = function () { - this.UI.innerHTML = '' - } - - loadOptionTags = () => { - - this.clearHtml() - - let temp_vtt_file_dir = '' - let optionNode - - optionNode = document.createElement('option') - optionNode.value = '' - optionNode.innerHTML = "Select a video to play" - this.UI.appendChild(optionNode) - - for (var k in vtt_files) { - var vtt_file = vtt_files[k] - var vtt_file_dir = vtt_file.split(/\//gi)[0] - - if (vtt_file_dir != temp_vtt_file_dir) { - optionNode = document.createElement('optgroup') - optionNode.label = Helper.validateTitle(vtt_file_dir) - this.UI.appendChild(optionNode) - } - - optionNode = document.createElement('option') - optionNode.value = k - optionNode.innerHTML = Helper.validateTitle(vtt_file.replace(vtt_file_dir + "/", '   ')) - this.UI.appendChild(optionNode) - temp_vtt_file_dir = vtt_file_dir - } - - this.UI.appendChild(optionNode) - } -} - -class Seekbar { - - constructor() { - this.UI = document.getElementById("seekbar"); - this.posUI = document.getElementById("seekbar_pos"); - this.UI.ontimeupdate = function () { - var percentage = (vid.currentTime / vid.duration) * 100; - $("#custom-seekbar span").css("width", percentage + "%"); - }; - - this.drag_started = false; - - this.posUI.onmousedown = (e) => { - console.log('onmousedown...'); - this.drag_started = true; - }; - this.UI.onmousemove = (e) => { - if (this.drag_started) { - console.log('onmousemove...'); - this.posUI.style.left = e.pageX; - } - }; - this.UI.onmouseup = (e) => { - console.log('onmouseup...'); - this.drag_started = false; - }; - this.posUI.onmouseup = (e) => { - console.log('onmouseup...'); - this.drag_started = false; - }; - } - - - bindEvents = (evName, callback) => { - this.UI.addEventListener(evName, callback) - this.UI[evName] = callback - } - -}; - - -document.ready(async (event) => { - - const tutorialList = new TutorialList() - const player = new Player() - - await tutorialList.load() - - tutorialList.bindEvents('change', (e) => { - Helper.setConf('currentTime', 0) - player.play(tutorialList.selectedValue()) - Helper.setConf('playedIndex', tutorialList.getIndex()) - }) - - player.bindEvents('onended', (args) => { - Helper.setConf('currentTime', 0) - let nextIndex = tutorialList.getIndex() + 1 - if (nextIndex < tutorialList.length()) { - tutorialList.setIndex(nextIndex) - player.play(tutorialList.selectedValue(), true) - } - }) - - if (playedIndex = Helper.getConf('playedIndex')) { - tutorialList.setIndex(parseInt(playedIndex)) - player.play(tutorialList.selectedValue(), false) - } - -}) - diff --git a/player/index.js b/player/index.js new file mode 100644 index 0000000..c5ff564 --- /dev/null +++ b/player/index.js @@ -0,0 +1,6 @@ +const Helper = require("./player/components/helper"); +const Player = require("./player/components/player"); +const TutorialList = require("./player/components/tutorial_list"); +const Bootstrap = require("./player/components/bootstrap"); + +Bootstrap.initialize(); diff --git a/utils/create_tutorial_list.js b/utils/create_tutorial_list.js new file mode 100644 index 0000000..b7cc927 --- /dev/null +++ b/utils/create_tutorial_list.js @@ -0,0 +1,62 @@ +const glob = require("glob"); +const path = require("path"); +const fs = require("fs"); + +function writeListFile(arrList, varName, fileName, dirPath) { + let newArrList = arrList.map(function(item) { + return item.replace(dirPath + "/", ""); + }); + let content = newArrList.join('",\n"'); + content = "var " + varName + ' = [\n"' + content + '"\n];'; + fs.writeFileSync(dirPath + "/" + fileName, content); +} + +function naturalSort(myArray) { + var collator = new Intl.Collator(undefined, { + numeric: true, + sensitivity: "base" + }); + return myArray.sort(collator.compare); +} + +function validateVTT(mp4_files, vtt_files) { + var new_vtt_files = []; + for( var i = 0; i < mp4_files.length; i++ ) { + var mp4_file = mp4_files[i]; + var vtt_file = mp4_file.substr(0, mp4_file.length - 4) + ".vtt"; + var srt_file = mp4_file.substr(0, mp4_file.length - 4) + ".srt"; + if (vtt_files.indexOf(vtt_file) > -1) { + new_vtt_files.push(vtt_file); + } else if (vtt_files.indexOf(srt_file) > -1) { + new_vtt_files.push(srt_file); + } else { + new_vtt_files.push(vtt_file); + } + } + return new_vtt_files; +} + +module.exports = (dirPath, cb) => { + let mp4_files = glob.sync(path.join(dirPath, "/**/*.mp4")); + let vtt_files = glob.sync(path.join(dirPath, "/**/*.vtt")); + let srt_files = glob.sync(path.join(dirPath, "/**/*.srt")); + + if ( vtt_files.length < srt_files.length ) { + vtt_files = srt_files; + } + + mp4_files = naturalSort(mp4_files); + + vtt_files = validateVTT(mp4_files, vtt_files); + + const newDirPath = dirPath.replace(/\\/g, "/"); + writeListFile(mp4_files, "mp4_files", "list_mp4.js", newDirPath); + + if (vtt_files.length > 0) { + writeListFile(vtt_files, "vtt_files", "list_vtt.js", newDirPath); + } else { + writeListFile([], "vtt_files", "list_vtt.js", newDirPath); + } + + cb({ result: true }); +};