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 });
+};