├── .electron-vue
├── build.js
├── dev-client.js
├── dev-main.js
├── dev-runner.js
├── webpack.main.config.js
└── webpack.renderer.config.js
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── appveyor.yml
├── cmake-tool
├── appCMakeJSConfig.js
├── buildSystem.js
├── cMake.js
├── cmLog.js
├── cpp
│ └── win_delay_load_hook.cc
├── electron-abi.js
├── environment.js
├── index.js
├── node-addon.cmake
├── processHelpers.js
├── targetOptions.js
├── toolset.js
└── vsDetect.js
├── ffplay.png
├── licenses
└── FFmpeg.md
├── node-ffplay
├── CMakeLists.txt
└── src
│ ├── player.cc
│ ├── player.h
│ └── wrap.cc
├── package.json
├── resources
└── icons
│ ├── 256x256.png
│ ├── icon.icns
│ └── icon.ico
├── src
├── components
│ ├── app-bar.vue
│ ├── app.vue
│ ├── dialogs.vue
│ └── xplayer
│ │ ├── controller.vue
│ │ ├── detection-canvas.js
│ │ ├── index.vue
│ │ ├── info-panel.vue
│ │ ├── loading.vue
│ │ ├── play-list.vue
│ │ ├── settings.vue
│ │ ├── test_decode.js
│ │ ├── video.vue
│ │ └── yuv-canvas.js
├── ffplay.js
├── i18n
│ ├── en.yml
│ ├── index.js
│ └── zh-cn.yml
├── import-dll.js
├── index.ejs
├── index.js
├── main
│ ├── index.dev.js
│ └── index.js
└── store
│ ├── index.js
│ └── video.js
└── test
├── e2e
├── index.js
├── specs
│ └── Launch.spec.js
└── utils.js
└── unit
├── index.js
├── karma.conf.js
└── specs
└── LandingPage.spec.js
/.electron-vue/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | const chalk = require('chalk')
6 | const del = require('del')
7 | const webpack = require('webpack')
8 | const Multispinner = require('multispinner')
9 |
10 | const mainConfig = require('./webpack.main.config')
11 | const rendererConfig = require('./webpack.renderer.config')
12 |
13 | const doneLog = chalk.bgGreen.white(' DONE ') + ' '
14 | const errorLog = chalk.bgRed.white(' ERROR ') + ' '
15 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
16 |
17 | if (process.env.BUILD_TARGET === 'clean') clean()
18 | else build()
19 |
20 | function clean () {
21 | del.sync(['release/*'])
22 | console.log(`\n${doneLog}\n`)
23 | process.exit()
24 | }
25 |
26 | function build () {
27 |
28 | del.sync(['dist/*', '!.gitkeep'])
29 |
30 | const tasks = ['main', 'renderer']
31 | const m = new Multispinner(tasks, {
32 | preText: 'building',
33 | postText: 'process'
34 | })
35 |
36 | let results = ''
37 |
38 | m.on('success', () => {
39 | process.stdout.write('\x1B[2J\x1B[0f')
40 | console.log(`\n\n${results}`)
41 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
42 | process.exit()
43 | })
44 |
45 | pack(mainConfig).then(result => {
46 | results += result + '\n\n'
47 | m.success('main')
48 | }).catch(err => {
49 | m.error('main')
50 | console.log(`\n ${errorLog}failed to build main process`)
51 | console.error(`\n${err}\n`)
52 | process.exit(1)
53 | })
54 |
55 | pack(rendererConfig).then(result => {
56 | results += result + '\n\n'
57 | m.success('renderer')
58 | }).catch(err => {
59 | m.error('renderer')
60 | console.log(`\n ${errorLog}failed to build renderer process`)
61 | console.error(`\n${err}\n`)
62 | process.exit(1)
63 | })
64 | }
65 |
66 | function pack (config) {
67 | return new Promise((resolve, reject) => {
68 | config.mode = 'production'
69 | webpack(config, (err, stats) => {
70 | if (err) reject(err.stack || err)
71 | else if (stats.hasErrors()) {
72 | let err = ''
73 |
74 | stats.toString({
75 | chunks: false,
76 | colors: true
77 | })
78 | .split(/\r?\n/)
79 | .forEach(line => {
80 | err += ` ${line}\n`
81 | })
82 |
83 | reject(err)
84 | } else {
85 | resolve(stats.toString({
86 | chunks: false,
87 | colors: true
88 | }))
89 | }
90 | })
91 | })
92 | }
93 |
--------------------------------------------------------------------------------
/.electron-vue/dev-client.js:
--------------------------------------------------------------------------------
1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
2 |
3 | hotClient.subscribe(event => {
4 | /**
5 | * Reload browser when HTMLWebpackPlugin emits a new index.html
6 | *
7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
8 | * https://github.com/SimulatedGREG/electron-vue/issues/437
9 | * https://github.com/jantimon/html-webpack-plugin/issues/680
10 | */
11 | // if (event.action === 'reload') {
12 | // window.location.reload()
13 | // }
14 |
15 | /**
16 | * Notify `mainWindow` when `main` process is compiling,
17 | * giving notice for an expected reload of the `electron` process
18 | */
19 | if (event.action === 'compiling') {
20 | document.body.innerHTML += `
21 |
34 |
35 |
36 | Compiling Main Process...
37 |
38 | `
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/.electron-vue/dev-main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is used specifically and only for development. It installs
3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to
4 | * modify this file, but it can be used to extend your development
5 | * environment.
6 | */
7 |
8 | /* eslint-disable */
9 |
10 | process.env.NODE_ENV = 'development';
11 |
12 | // Install `electron-debug` with `devtron`
13 | require('electron-debug')()
14 |
15 | // Install `vue-devtools`
16 | require('electron').app.on('ready', () => {
17 | let installExtension = require('electron-devtools-installer')
18 | installExtension.default(installExtension.VUEJS_DEVTOOLS)
19 | .then(() => {})
20 | .catch(err => {
21 | console.log('Unable to install `vue-devtools`: \n', err)
22 | })
23 | })
24 |
25 | // Require `main` process to boot app
26 | require('../src/main')
27 |
--------------------------------------------------------------------------------
/.electron-vue/dev-runner.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const chalk = require('chalk')
4 | const electron = require('electron')
5 | const path = require('path')
6 | const { spawn } = require('child_process')
7 | const webpack = require('webpack')
8 | const WebpackDevServer = require('webpack-dev-server')
9 | const webpackHotMiddleware = require('webpack-hot-middleware')
10 |
11 | const mainConfig = require('./webpack.main.config')
12 | const rendererConfig = require('./webpack.renderer.config')
13 |
14 | let electronProcess = null
15 | let manualRestart = false
16 | let hotMiddleware
17 |
18 | function logStats (proc, data) {
19 | let log = ''
20 |
21 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
22 | log += '\n\n'
23 |
24 | if (typeof data === 'object') {
25 | data.toString({
26 | colors: true,
27 | chunks: false
28 | }).split(/\r?\n/).forEach(line => {
29 | log += ' ' + line + '\n'
30 | })
31 | } else {
32 | log += ` ${data}\n`
33 | }
34 |
35 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n'
36 |
37 | console.log(log)
38 | }
39 |
40 | function startMain () {
41 | return new Promise((resolve, reject) => {
42 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
43 | mainConfig.mode = 'development'
44 | const compiler = webpack(mainConfig)
45 |
46 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
47 | logStats('Main', chalk.white.bold('compiling...'))
48 | hotMiddleware.publish({ action: 'compiling' })
49 | done()
50 | })
51 |
52 | compiler.watch({}, (err, stats) => {
53 | if (err) {
54 | console.log(err)
55 | return
56 | }
57 |
58 | logStats('Main', stats)
59 |
60 | if (electronProcess && electronProcess.kill) {
61 | manualRestart = true
62 | process.kill(electronProcess.pid)
63 | electronProcess = null
64 | startElectron()
65 |
66 | setTimeout(() => {
67 | manualRestart = false
68 | }, 5000)
69 | }
70 |
71 | resolve()
72 | })
73 | })
74 | }
75 |
76 | function startMainForTest () {
77 | return new Promise((resolve, reject) => {
78 | mainConfig.entry.main = path.join(__dirname, '../src/main/test')
79 | mainConfig.mode = 'development'
80 | const compiler = webpack(mainConfig)
81 |
82 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
83 | logStats('Main', chalk.white.bold('compiling...'))
84 | done()
85 | })
86 |
87 | compiler.watch({}, (err, stats) => {
88 | if (err) {
89 | console.log(err)
90 | return
91 | }
92 |
93 | logStats('Main', stats)
94 | resolve()
95 | })
96 | })
97 | }
98 |
99 | function startRenderer () {
100 | return new Promise((resolve, reject) => {
101 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
102 | rendererConfig.mode = 'development'
103 | const compiler = webpack(rendererConfig)
104 | hotMiddleware = webpackHotMiddleware(compiler, {
105 | log: false,
106 | heartbeat: 2500
107 | })
108 |
109 | compiler.hooks.compilation.tap('compilation', compilation => {
110 | compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
111 | hotMiddleware.publish({ action: 'reload' })
112 | cb()
113 | })
114 | })
115 |
116 | compiler.hooks.done.tap('done', stats => {
117 | logStats('Renderer', stats)
118 | })
119 |
120 | const server = new WebpackDevServer(
121 | compiler,
122 | {
123 | contentBase: path.join(__dirname, '../'),
124 | quiet: true,
125 | before (app, ctx) {
126 | app.use(hotMiddleware)
127 | ctx.middleware.waitUntilValid(() => {
128 | resolve()
129 | })
130 | }
131 | }
132 | )
133 |
134 | server.listen(9080)
135 | })
136 | }
137 |
138 | function startElectron () {
139 | var args = [
140 | '--inspect=5858',
141 | path.join(__dirname, '../dist/electron/main.js')
142 | ]
143 |
144 | // detect yarn or npm and process commandline args accordingly
145 | if (process.env.npm_execpath.endsWith('yarn.js')) {
146 | args = args.concat(process.argv.slice(3))
147 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
148 | args = args.concat(process.argv.slice(2))
149 | }
150 |
151 | electronProcess = spawn(electron, args)
152 |
153 | electronProcess.stdout.on('data', data => {
154 | electronLog(data, 'blue')
155 | })
156 | electronProcess.stderr.on('data', data => {
157 | electronLog(data, 'red')
158 | })
159 |
160 | electronProcess.on('close', () => {
161 | if (!manualRestart) process.exit()
162 | })
163 | }
164 |
165 | function electronLog (data, color) {
166 | let log = ''
167 | data = data.toString().split(/\r?\n/)
168 | data.forEach(line => {
169 | log += ` ${line}\n`
170 | })
171 | if (/[0-9A-z]+/.test(log)) {
172 | console.log(
173 | chalk[color].bold('┏ Electron -------------------') +
174 | '\n\n' +
175 | log +
176 | chalk[color].bold('┗ ----------------------------') +
177 | '\n'
178 | )
179 | }
180 | }
181 |
182 | function init () {
183 |
184 | startRenderer()
185 | .then(() => startMain())
186 | .then(() => startElectron())
187 | .catch(err => {
188 | console.error(err)
189 | })
190 | }
191 |
192 | function initForTest () {
193 | startMainForTest()
194 | .then(() => startElectron())
195 | .catch(err => {
196 | console.error(err)
197 | })
198 | }
199 |
200 | if (process.argv[2] == 'test-main') {
201 | initForTest()
202 | } else {
203 | init()
204 | }
205 |
--------------------------------------------------------------------------------
/.electron-vue/webpack.main.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'main'
4 |
5 | const path = require('path')
6 | const { dependencies } = require('../package.json')
7 | const webpack = require('webpack')
8 |
9 | const MinifyPlugin = require("babel-minify-webpack-plugin")
10 |
11 | let mainConfig = {
12 | entry: {
13 | main: path.join(__dirname, '../src/main')
14 | },
15 | externals: [
16 | ...Object.keys(dependencies || {})
17 | ],
18 | module: {
19 | rules: [
20 | {
21 | test: /\.(js)$/,
22 | enforce: 'pre',
23 | exclude: /node_modules/,
24 | use: {
25 | loader: 'eslint-loader',
26 | options: {
27 | formatter: require('eslint-friendly-formatter')
28 | }
29 | }
30 | },
31 | {
32 | test: /\.js$/,
33 | exclude: /node_modules/,
34 | use: [
35 | {
36 | loader: 'babel-loader',
37 | options: {
38 | presets: [['@babel/env', {
39 | 'targets': {
40 | node: 8
41 | }
42 | }]],
43 | plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-transform-runtime'],
44 | sourceType: 'unambiguous'
45 | }
46 | }
47 | ]
48 | },
49 | {
50 | test: /\.node$/,
51 | use: 'node-loader'
52 | }
53 | ]
54 | },
55 | node: {
56 | __dirname: process.env.NODE_ENV !== 'production',
57 | __filename: process.env.NODE_ENV !== 'production'
58 | },
59 | output: {
60 | filename: '[name].js',
61 | libraryTarget: 'commonjs2',
62 | path: path.join(__dirname, '../dist/electron')
63 | },
64 | plugins: [
65 | new webpack.NoEmitOnErrorsPlugin()
66 | ],
67 | resolve: {
68 | extensions: ['.js', '.json', '.node']
69 | },
70 | target: 'electron-main'
71 | }
72 |
73 | /**
74 | * Adjust mainConfig for development settings
75 | */
76 | if (process.env.NODE_ENV !== 'production') {
77 | mainConfig.plugins.push(
78 | new webpack.DefinePlugin({
79 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
80 | })
81 | )
82 | }
83 |
84 | /**
85 | * Adjust mainConfig for production settings
86 | */
87 | if (process.env.NODE_ENV === 'production') {
88 | mainConfig.plugins.push(
89 | new MinifyPlugin(),
90 | new webpack.DefinePlugin({
91 | 'process.env.NODE_ENV': '"production"'
92 | })
93 | )
94 | }
95 |
96 | module.exports = mainConfig
97 |
--------------------------------------------------------------------------------
/.electron-vue/webpack.renderer.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'renderer'
4 |
5 | const path = require('path')
6 | const { dependencies } = require('../package.json')
7 | const webpack = require('webpack')
8 |
9 | const BabiliWebpackPlugin = require('babili-webpack-plugin')
10 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
11 | const HtmlWebpackPlugin = require('html-webpack-plugin')
12 | const { VueLoaderPlugin } = require('vue-loader')
13 |
14 | /**
15 | * List of node_modules to include in webpack bundle
16 | *
17 | * Required for specific packages like Vue UI libraries
18 | * that provide pure *.vue files that need compiling
19 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
20 | */
21 | let whiteListedModules = ['vue', 'vuetify']
22 |
23 | let rendererConfig = {
24 | devtool: '#cheap-module-eval-source-map',
25 | entry: {
26 | renderer: path.join(__dirname, '..', 'src')
27 | },
28 | externals: [
29 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
30 | ],
31 | module: {
32 | rules: [
33 | {
34 | test: /\.(yml|yaml)$/,
35 | loader: 'yml-loader'
36 | },
37 | {
38 | test: /\.(js|vue)$/,
39 | enforce: 'pre',
40 | exclude: /node_modules|output-filter|node-scheduler|object-detector/,
41 | use: {
42 | loader: 'eslint-loader',
43 | options: {
44 | formatter: require('eslint-friendly-formatter')
45 | }
46 | }
47 | },
48 | {
49 | test: /\.scss$/,
50 | use: ['vue-style-loader', 'css-loader', 'sass-loader']
51 | },
52 | {
53 | test: /\.sass$/,
54 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax']
55 | },
56 | {
57 | test: /\.less$/,
58 | use: ['vue-style-loader', 'css-loader', 'less-loader']
59 | },
60 | {
61 | test: /\.css$/,
62 | use: ['vue-style-loader', 'css-loader']
63 | },
64 | {
65 | test: /\.html$/,
66 | use: 'vue-html-loader'
67 | },
68 | {
69 | test: /\.js$/,
70 | exclude: /node_modules/,
71 | use: [
72 | {
73 | loader: 'babel-loader',
74 | options: {
75 | presets: ['@babel/env'],
76 | plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-transform-runtime'],
77 | sourceType: 'unambiguous'
78 | }
79 | }
80 | ]
81 | },
82 | {
83 | test: /\.vue$/,
84 | use: {
85 | loader: 'vue-loader',
86 | options: {
87 | extractCSS: process.env.NODE_ENV === 'production',
88 | loaders: {
89 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
90 | scss: 'vue-style-loader!css-loader!sass-loader',
91 | less: 'vue-style-loader!css-loader!less-loader'
92 | }
93 | }
94 | }
95 | },
96 | {
97 | test: /\.node$/,
98 | use: 'node-loader'
99 | },
100 | {
101 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
102 | use: {
103 | loader: 'url-loader',
104 | query: {
105 | limit: 10000,
106 | name: 'imgs/[name]--[folder].[ext]'
107 | }
108 | }
109 | },
110 | {
111 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
112 | loader: 'url-loader',
113 | options: {
114 | limit: 10000,
115 | name: 'media/[name]--[folder].[ext]'
116 | }
117 | },
118 | {
119 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
120 | use: {
121 | loader: 'url-loader',
122 | query: {
123 | limit: 10000,
124 | name: 'fonts/[name]--[folder].[ext]'
125 | }
126 | }
127 | }
128 | ]
129 | },
130 | node: {
131 | __dirname: process.env.NODE_ENV !== 'production',
132 | __filename: process.env.NODE_ENV !== 'production'
133 | },
134 | plugins: [
135 | new VueLoaderPlugin(),
136 | new MiniCssExtractPlugin({filename: 'styles.css'}),
137 | new HtmlWebpackPlugin({
138 | filename: 'index.html',
139 | template: path.resolve(__dirname, '../src/index.ejs'),
140 | inject: false,
141 | minify: {
142 | collapseWhitespace: true,
143 | removeAttributeQuotes: true,
144 | removeComments: true
145 | },
146 | nodeModules: process.env.NODE_ENV !== 'production'
147 | ? path.resolve(__dirname, '../node_modules')
148 | : false
149 | }),
150 | new webpack.HotModuleReplacementPlugin(),
151 | new webpack.NoEmitOnErrorsPlugin()
152 | ],
153 | output: {
154 | filename: '[name].js',
155 | libraryTarget: 'commonjs2',
156 | path: path.join(__dirname, '../dist/electron')
157 | },
158 | resolve: {
159 | alias: {
160 | '@': path.join(__dirname, '../src'),
161 | 'vue$': 'vue/dist/vue.esm.js'
162 | },
163 | extensions: ['.js', '.vue', '.json', '.css', '.node']
164 | },
165 | target: 'electron-renderer'
166 | }
167 |
168 | /**
169 | * Adjust rendererConfig for development settings
170 | */
171 | if (process.env.NODE_ENV !== 'production') {
172 | rendererConfig.plugins.push(
173 | new webpack.DefinePlugin({
174 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
175 | })
176 | )
177 | }
178 |
179 | /**
180 | * Adjust rendererConfig for production settings
181 | */
182 | if (process.env.NODE_ENV === 'production') {
183 | rendererConfig.devtool = ''
184 |
185 | rendererConfig.plugins.push(
186 | new BabiliWebpackPlugin(),
187 | new webpack.DefinePlugin({
188 | 'process.env.NODE_ENV': '"production"'
189 | }),
190 | new webpack.LoaderOptionsPlugin({
191 | minimize: true
192 | })
193 | )
194 | }
195 |
196 | module.exports = rendererConfig
197 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: 'babel-eslint',
4 | parserOptions: {
5 | sourceType: 'module'
6 | },
7 | env: {
8 | browser: true,
9 | node: true
10 | },
11 | extends: 'standard',
12 | globals: {
13 | __static: true
14 | },
15 | plugins: [
16 | 'html'
17 | ],
18 | 'rules': {
19 | // allow paren-less arrow functions
20 | 'arrow-parens': 0,
21 | // allow async-await
22 | 'generator-star-spacing': 0,
23 | // allow debugger during development
24 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
25 |
26 | 'semi': 0,
27 | 'no-trailing-spaces': 0,
28 | 'camelcase': 0
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | release
3 | dist
4 | build
5 | coverage
6 | node_modules
7 | npm-debug.log
8 | npm-debug.log.*
9 | thumbs.db
10 | !.gitkeep
11 | adbkey
12 | package-lock.json
13 | report.html
14 | output.xml
15 | log.html
16 | *.pyc
17 | release
18 | bin
19 | node-scheduler
20 | .vscode
21 | *.node
22 | *.dll
23 | sign.cer
24 | node_cache
25 | .third-party
26 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | electron_mirror=https://npm.taobao.org/mirrors/electron/
2 | registry=https://registry.npm.taobao.org
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 R.J. Zhou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple video player using electron & ffplay (as node addon)
2 |
3 | 
4 |
5 | # License
6 | * [FFMpeg] (https://ffmpeg.org/legal.html)
7 | * [cmake-js] (https://github.com/cmake-js/cmake-js)
8 | * [cmake-node-module] (https://github.com/mapbox/cmake-node-module/tree/master)
9 |
10 | # Requirements
11 | - Windows 10 x64 (not tested on other platforms)
12 | - CMake 3.9 or above
13 | - Visual Studio 2017/2019
14 | - nodejs 12.x.x or above
15 |
16 | - https://github.com/ShiftMediaProject/SDL/releases/download/release-2.0.14/libsdl_release-2.0.14_msvc15.zip
17 | - https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2021-01-14-13-18/ffmpeg-n4.3.1-29-g89daac5fe2-win64-lgpl-shared-4.3.zip
18 |
19 | uncompress libsdl2*.zip & ffmpeg*.zip to electron-ffplay/.third-party/prebuilt
20 |
21 | put .third_party/prebuilt/bin[/x64]/*.dll in electron-ffplay/
22 |
23 | # build
24 | ```bash
25 | # get electron-ffplay source
26 | cd electron-ffplay
27 |
28 | # install dependencies
29 | npm i
30 |
31 | # build addon
32 | npm run configure:ffplay
33 | npm run build:ffplay
34 |
35 | # run
36 | npm run dev
37 |
38 | ```
39 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # Commented sections below can be used to run tests on the CI server
2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
3 | version: 0.1.{build}
4 |
5 | branches:
6 | only:
7 | - master
8 |
9 | image: Visual Studio 2017
10 | platform:
11 | - x64
12 |
13 | cache:
14 | - node_modules
15 | - '%APPDATA%\npm-cache'
16 | - '%USERPROFILE%\.electron'
17 | - '%USERPROFILE%\AppData\Local\Yarn\cache'
18 |
19 | init:
20 | - git config --global core.autocrlf input
21 |
22 | install:
23 | - ps: Install-Product node 8 x64
24 | - git reset --hard HEAD
25 | - yarn
26 | - node --version
27 |
28 | build_script:
29 | #- yarn test
30 | - yarn build
31 |
32 | test: off
33 |
--------------------------------------------------------------------------------
/cmake-tool/appCMakeJSConfig.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let path = require("path");
3 | let _ = require("lodash");
4 |
5 | function getConfig(lookPath, log) {
6 | let pjsonPath = path.join(lookPath, "package.json");
7 | log.silly("CFG", "Looking for package.json in: '" + pjsonPath + "'.");
8 | try {
9 | let json = require(pjsonPath);
10 | log.silly("CFG", "Loaded:\n" + JSON.stringify(json));
11 | if (_.isPlainObject(json) && _.isPlainObject(json["cmake-js"])) {
12 | log.silly("CFG", "Config found.");
13 | return json["cmake-js"];
14 | }
15 | else {
16 | log.silly("CFG", "Config not found.");
17 | return null;
18 | }
19 | }
20 | catch (e) {
21 | log.silly("CFG", "'package.json' not found.");
22 | return null;
23 | }
24 | }
25 |
26 | module.exports = function (projectPath, log) {
27 | log.verbose("CFG", "Looking for application level CMake.js config in '" + projectPath + ".");
28 | let currPath = projectPath;
29 | let lastConfig = null;
30 | let currConfig;
31 | for (; ;) {
32 | currConfig = getConfig(currPath, log);
33 | if (currConfig) {
34 | lastConfig = currConfig;
35 | }
36 | try {
37 | log.silly("CFG", "Looking for parent path.");
38 | let lastPath = currPath;
39 | currPath = path.normalize(path.join(currPath, ".."));
40 | if (lastPath === currPath) {
41 | currPath = null; // root
42 | }
43 | if (currPath) {
44 | log.silly("CFG", "Parent path: '" + currPath + "'.");
45 | }
46 | }
47 | catch (e) {
48 | log.silly("CFG", "Exception:\n" + e.stack);
49 | break;
50 | }
51 | if (currPath === null) {
52 | log.silly("CFG", "Parent path with package.json file doesn't exists. Done.");
53 | break;
54 | }
55 | }
56 | if (lastConfig) {
57 | log.verbose("CFG", "Application level CMake.js config found:\n" + JSON.stringify(lastConfig));
58 | }
59 | else {
60 | log.verbose("CFG", "Application level CMake.js config doesn't exists.");
61 | }
62 | return lastConfig;
63 | };
64 |
--------------------------------------------------------------------------------
/cmake-tool/buildSystem.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let CMake = require("./cMake");
3 | let CMLog = require("./cmLog");
4 | let appCMakeJSConfig = require("./appCMakeJSConfig");
5 | let path = require("path");
6 | let _ = require("lodash");
7 | let Toolset = require("./toolset");
8 |
9 | function BuildSystem(options) {
10 | this.options = options || {};
11 | this.options.directory = path.resolve(this.options.directory || process.cwd());
12 | this.log = new CMLog(this.options);
13 | let appConfig = appCMakeJSConfig(this.options.directory, this.log);
14 | if (_.isPlainObject(appConfig)) {
15 | if (_.keys(appConfig).length) {
16 | this.log.verbose("CFG", "Applying CMake.js config from root package.json:");
17 | this.log.verbose("CFG", JSON.stringify(appConfig));
18 | this.options.arch = this.options.arch || appConfig.arch;
19 | }
20 | }
21 | this.log.verbose("CFG", "Build system options:");
22 | this.log.verbose("CFG", JSON.stringify(this.options));
23 | this.cmake = new CMake(this.options);
24 | this.toolset = new Toolset(this.options);
25 | }
26 |
27 | BuildSystem.prototype._ensureInstalled = async function () {
28 | try {
29 | await this.toolset.initialize(true);
30 | }
31 | catch (e) {
32 | this._showError(e);
33 | throw e;
34 | }
35 | };
36 |
37 | BuildSystem.prototype._showError = function (e) {
38 | if (this.log.level === "verbose" || this.log.level === "silly") {
39 | this.log.error("OMG", e.stack);
40 | }
41 | else {
42 | this.log.error("OMG", e.message);
43 | }
44 | };
45 |
46 | BuildSystem.prototype._invokeCMake = async function (method) {
47 | try {
48 | await this._ensureInstalled();
49 | return await this.cmake[method]();
50 | }
51 | catch (e) {
52 | this._showError(e);
53 | throw e;
54 | }
55 | };
56 |
57 | BuildSystem.prototype.getConfigureCommand = function () {
58 | return this._invokeCMake("getConfigureCommand");
59 | };
60 |
61 | BuildSystem.prototype.configure = function () {
62 | return this._invokeCMake("configure");
63 | };
64 |
65 | BuildSystem.prototype.getBuildCommand = function () {
66 | return this._invokeCMake("getBuildCommand");
67 | };
68 |
69 | BuildSystem.prototype.build = function () {
70 | return this._invokeCMake("build");
71 | };
72 |
73 | BuildSystem.prototype.getCleanCommand = function () {
74 | return this._invokeCMake("getCleanCommand");
75 | };
76 |
77 | BuildSystem.prototype.clean = function () {
78 | return this._invokeCMake("clean");
79 | };
80 |
81 | BuildSystem.prototype.reconfigure = function () {
82 | return this._invokeCMake("reconfigure");
83 | };
84 |
85 | BuildSystem.prototype.rebuild = function () {
86 | return this._invokeCMake("rebuild");
87 | };
88 |
89 | BuildSystem.prototype.compile = function () {
90 | return this._invokeCMake("compile");
91 | };
92 |
93 | module.exports = BuildSystem;
94 |
--------------------------------------------------------------------------------
/cmake-tool/cMake.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let which = require("which");
3 | let fs = require("fs-extra");
4 | let path = require("path");
5 | let _ = require("lodash");
6 | let environment = require("./environment");
7 | let CMLog = require("./cmLog");
8 | let TargetOptions = require("./targetOptions");
9 | let processHelpers = require("./processHelpers");
10 | let npmConfigData = require("rc")("npm");
11 | let Toolset = require("./toolset");
12 | const electron = require('electron')
13 | const {execFileSync} = require('child_process')
14 |
15 | const execResult = execFileSync(electron, [path.resolve(__dirname, 'electron-abi.js')])
16 | const ABI = execResult.toString().trim()
17 |
18 | function CMake(options) {
19 | this.options = options || {};
20 | this.log = new CMLog(this.options);
21 | this.projectRoot = path.resolve(this.options.directory || process.cwd());
22 | this.workDir = path.resolve(this.options.out || path.join(this.projectRoot, "build"));
23 | this.config = this.options.debug ? "Debug" : "Release";
24 | this.buildDir = path.join(this.workDir, this.config);
25 | this._isAvailable = null;
26 | this.targetOptions = new TargetOptions(this.options);
27 | this.toolset = new Toolset(this.options);
28 | this.cMakeOptions = this.options.cMakeOptions || {};
29 | this.silent = !!options.silent;
30 | }
31 |
32 | Object.defineProperties(CMake.prototype, {
33 | path: {
34 | get: function () {
35 | return this.options.cmakePath || "cmake";
36 | }
37 | },
38 | isAvailable: {
39 | get: function () {
40 | if (this._isAvailable === null) {
41 | this._isAvailable = CMake.isAvailable(this.options);
42 | }
43 | return this._isAvailable;
44 | }
45 | }
46 | });
47 |
48 | CMake.isAvailable = function (options) {
49 | options = options || {};
50 | try {
51 | if (options.cmakePath) {
52 | let stat = fs.lstatSync(options.cmakePath);
53 | return !stat.isDirectory();
54 | }
55 | else {
56 | which.sync("cmake");
57 | return true;
58 | }
59 | }
60 | catch (e) {
61 | _.noop(e);
62 | }
63 | return false;
64 | };
65 |
66 | CMake.getGenerators = async function (options) {
67 | let arch = " [arch]";
68 | options = options || {};
69 | let gens = [];
70 | if (CMake.isAvailable(options)) {
71 | // try parsing machine-readable capabilities (available since CMake 3.7)
72 | try {
73 | let stdout = await processHelpers.exec((options.cmakePath || "cmake") + " -E capabilities");
74 | let capabilities = JSON.parse(stdout);
75 | return capabilities.generators.map(x => x.name);
76 | }
77 | catch (error) {
78 | this.log.verbose("TOOL", "Failed to query CMake capabilities (CMake is probably older than 3.7)");
79 | }
80 |
81 | // fall back to parsing help text
82 | let stdout = await processHelpers.exec((options.cmakePath || "cmake") + " --help");
83 | let hasCr = stdout.includes("\r\n");
84 | let output = hasCr ? stdout.split("\r\n") : stdout.split("\n");
85 | let on = false;
86 | output.forEach(function (line, i) {
87 | if (on) {
88 | let parts = line.split("=");
89 | if ((parts.length === 2 && parts[0].trim()) ||
90 | (parts.length === 1 && i !== output.length - 1 && output[i + 1].trim()[0] === "=")) {
91 | let gen = parts[0].trim();
92 | if (_.endsWith(gen, arch)) {
93 | gen = gen.substr(0, gen.length - arch.length);
94 | }
95 | gens.push(gen);
96 | }
97 | }
98 | if (line.trim() === "Generators") {
99 | on = true;
100 | }
101 | });
102 | }
103 | else {
104 | throw new Error("CMake is not installed. Install CMake.");
105 | }
106 | return gens;
107 | };
108 |
109 | CMake.prototype.getGenerators = function () {
110 | return CMake.getGenerators(this.options);
111 | };
112 |
113 | CMake.prototype.verifyIfAvailable = function () {
114 | if (!this.isAvailable) {
115 | throw new Error("CMake executable is not found. Please use your system's package manager to install it, or you can get installers from there: http://cmake.org.");
116 | }
117 | };
118 |
119 | CMake.prototype.getConfigureCommand = async function () {
120 | // Create command:
121 | let command = this.path;
122 | command += " \"" + this.projectRoot + "\" --no-warn-unused-cli";
123 |
124 | let D = [];
125 |
126 | // CMake.js watermark
127 | D.push({"NODE_ABI": ABI});
128 |
129 | // Build configuration:
130 | D.push({"CMAKE_BUILD_TYPE": this.config});
131 | if (environment.isWin) {
132 | D.push({"CMAKE_RUNTIME_OUTPUT_DIRECTORY": this.workDir});
133 | }
134 | else {
135 | D.push({"CMAKE_LIBRARY_OUTPUT_DIRECTORY": this.buildDir});
136 | }
137 |
138 | // Runtime:
139 | D.push({"NODE_ARCH": this.targetOptions.arch});
140 |
141 | // Custom options
142 | for (let k of _.keys(this.cMakeOptions)) {
143 | D.push({[k]: this.cMakeOptions[k]});
144 | }
145 |
146 | // Sources:
147 | let srcPaths = [];
148 | if (environment.isWin) {
149 | let delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc'));
150 |
151 | srcPaths.push(delayHook.replace(/\\/gm, '/'));
152 | }
153 |
154 | D.push({"DELAY_LOAD_SRC": srcPaths.join(";")});
155 |
156 | // Toolset:
157 | await this.toolset.initialize(false);
158 |
159 | if (this.toolset.generator) {
160 | command += " -G\"" + this.toolset.generator + "\"";
161 | }
162 | if (this.toolset.platform) {
163 | command += " -A\"" + this.toolset.platform + "\"";
164 | }
165 | if (this.toolset.toolset) {
166 | command += " -T\"" + this.toolset.toolset + "\"";
167 | }
168 | if (this.toolset.cppCompilerPath) {
169 | D.push({"CMAKE_CXX_COMPILER": this.toolset.cppCompilerPath});
170 | }
171 | if (this.toolset.cCompilerPath) {
172 | D.push({"CMAKE_C_COMPILER": this.toolset.cCompilerPath});
173 | }
174 | if (this.toolset.compilerFlags.length) {
175 | D.push({"CMAKE_CXX_FLAGS": this.toolset.compilerFlags.join(" ")});
176 | }
177 | if (this.toolset.makePath) {
178 | D.push({"CMAKE_MAKE_PROGRAM": this.toolset.makePath});
179 | }
180 |
181 | // Load NPM config
182 | for (let key of _.keys(npmConfigData)) {
183 | if (_.startsWith(key, "cmake_")) {
184 | let s = {};
185 | let sk = key.substr(6);
186 | if (sk) {
187 | s[sk] = npmConfigData[key];
188 | if (s[sk]) {
189 | D.push(s);
190 | }
191 | }
192 | }
193 | }
194 |
195 | command += " " +
196 | D.map(function (p) {
197 | return "-D" + _.keys(p)[0] + "=\"" + _.values(p)[0] + "\"";
198 | }).join(" ");
199 |
200 | return command;
201 | };
202 |
203 | CMake.prototype.configure = async function () {
204 | this.verifyIfAvailable();
205 |
206 | this.log.info("CMD", "CONFIGURE");
207 | let listPath = path.join(this.projectRoot, "CMakeLists.txt");
208 | let command = await this.getConfigureCommand();
209 |
210 | try {
211 | await fs.lstat(listPath);
212 | }
213 | catch (e) {
214 | throw new Error("'" + listPath + "' not found.");
215 | }
216 |
217 | try {
218 | await fs.ensureDir(this.workDir);
219 | }
220 | catch (e) {
221 | _.noop(e);
222 | }
223 |
224 | let cwd = process.cwd();
225 | process.chdir(this.workDir);
226 | try {
227 | await this._run(command);
228 | }
229 | finally {
230 | process.chdir(cwd);
231 | }
232 | };
233 |
234 | CMake.prototype.ensureConfigured = async function () {
235 | try {
236 | await fs.lstat(path.join(this.workDir, "CMakeCache.txt"));
237 | }
238 | catch (e) {
239 | _.noop(e);
240 | await this.configure();
241 | }
242 | };
243 |
244 | CMake.prototype.getBuildCommand = function () {
245 | var command = this.path + " --build \"" + this.workDir + "\" --config " + this.config;
246 | if (this.options.target) {
247 | command += " --target " + this.options.target;
248 | }
249 | return Promise.resolve(command);
250 | };
251 |
252 | CMake.prototype.build = async function () {
253 | this.verifyIfAvailable();
254 |
255 | await this.ensureConfigured();
256 | let buildCommand = await this.getBuildCommand();
257 | this.log.info("CMD", "BUILD");
258 | await this._run(buildCommand);
259 | };
260 |
261 | CMake.prototype.getCleanCommand = function () {
262 | return this.path + " -E remove_directory \"" + this.workDir + "\"";
263 | };
264 |
265 | CMake.prototype.clean = function () {
266 | this.verifyIfAvailable();
267 |
268 | this.log.info("CMD", "CLEAN");
269 | return this._run(this.getCleanCommand());
270 | };
271 |
272 | CMake.prototype.reconfigure = async function () {
273 | await this.clean();
274 | await this.configure();
275 | };
276 |
277 | CMake.prototype.rebuild = async function () {
278 | await this.clean();
279 | await this.build();
280 | };
281 |
282 | CMake.prototype.compile = async function () {
283 | try {
284 | await this.build();
285 | }
286 | catch (e) {
287 | _.noop(e);
288 | this.log.info("REP", "Build has been failed, trying to do a full rebuild.");
289 | await this.rebuild();
290 | }
291 | };
292 |
293 | CMake.prototype._run = function (command) {
294 | this.log.info("RUN", command);
295 | return processHelpers.run(command, {silent: this.silent});
296 | };
297 |
298 | module.exports = CMake;
299 |
--------------------------------------------------------------------------------
/cmake-tool/cmLog.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let log = require("npmlog");
3 |
4 | function CMLog(options) {
5 | this.options = options || {};
6 | this.debug = require("debug")(this.options.logName || "cmake-js");
7 | }
8 |
9 | Object.defineProperties(CMLog.prototype, {
10 | level: {
11 | get: function() {
12 | if (this.options.noLog) {
13 | return "silly";
14 | }
15 | else {
16 | return log.level;
17 | }
18 | }
19 | }
20 | });
21 |
22 | CMLog.prototype.silly = function(cat, msg) {
23 | if (this.options.noLog) {
24 | this.debug(cat + ": " + msg);
25 | }
26 | else {
27 | log.silly(cat, msg);
28 | }
29 | };
30 |
31 | CMLog.prototype.verbose = function(cat, msg) {
32 | if (this.options.noLog) {
33 | this.debug(cat + ": " + msg);
34 | }
35 | else {
36 | log.verbose(cat, msg);
37 | }
38 | };
39 |
40 | CMLog.prototype.info = function(cat, msg) {
41 | if (this.options.noLog) {
42 | this.debug(cat + ": " + msg);
43 | }
44 | else {
45 | log.info(cat, msg);
46 | }
47 | };
48 |
49 | CMLog.prototype.warn = function(cat, msg) {
50 | if (this.options.noLog) {
51 | this.debug(cat + ": " + msg);
52 | }
53 | else {
54 | log.warn(cat, msg);
55 | }
56 | };
57 |
58 | CMLog.prototype.http = function(cat, msg) {
59 | if (this.options.noLog) {
60 | this.debug(cat + ": " + msg);
61 | }
62 | else {
63 | log.http(cat, msg);
64 | }
65 | };
66 |
67 | CMLog.prototype.error = function(cat, msg) {
68 | if (this.options.noLog) {
69 | this.debug(cat + ": " + msg);
70 | }
71 | else {
72 | log.error(cat, msg);
73 | }
74 | };
75 |
76 | module.exports = CMLog;
--------------------------------------------------------------------------------
/cmake-tool/cpp/win_delay_load_hook.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * When this file is linked to a DLL, it sets up a delay-load hook that
3 | * intervenes when the DLL is trying to load 'node.exe' or 'iojs.exe'
4 | * dynamically. Instead of trying to locate the .exe file it'll just return
5 | * a handle to the process image.
6 | *
7 | * This allows compiled addons to work when node.exe or iojs.exe is renamed.
8 | */
9 |
10 | #ifdef _MSC_VER
11 |
12 | #ifndef WIN32_LEAN_AND_MEAN
13 | #define WIN32_LEAN_AND_MEAN
14 | #endif
15 |
16 | #include
17 |
18 | #include
19 | #include
20 |
21 | static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo* info) {
22 | HMODULE m;
23 | if (event != dliNotePreLoadLibrary)
24 | return NULL;
25 |
26 | if (_stricmp(info->szDll, "iojs.exe") != 0 &&
27 | _stricmp(info->szDll, "node.exe") != 0)
28 | return NULL;
29 |
30 | m = GetModuleHandle(NULL);
31 | return (FARPROC) m;
32 | }
33 |
34 | decltype(__pfnDliNotifyHook2) __pfnDliNotifyHook2 = load_exe_hook;
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/cmake-tool/electron-abi.js:
--------------------------------------------------------------------------------
1 | const {app} = require('electron')
2 | const nodeMajor2Abis = {
3 | '8': 57,
4 | '9': 59,
5 | '10': 64,
6 | '11': 64,
7 | '12': 72,
8 | '13': 79,
9 | '14': 83,
10 | '15': 88
11 | }
12 |
13 | app.allowRendererProcessReuse = false;
14 |
15 | const ABI = nodeMajor2Abis[process.versions.node.split('.')[0]]
16 | console.log(ABI)
17 | app.quit()
18 |
--------------------------------------------------------------------------------
/cmake-tool/environment.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let os = require("os");
3 | let which = require("which");
4 | let _ = require("lodash");
5 |
6 | let environment = module.exports = {
7 | moduleVersion: require("../package.json").version,
8 | platform: os.platform(),
9 | isWin: os.platform() === "win32",
10 | isLinux: os.platform() === "linux",
11 | isOSX: os.platform() === "darwin",
12 | arch: os.arch(),
13 | isX86: os.arch() === "ia32",
14 | isX64: os.arch() === "x64",
15 | isArm: os.arch() === "arm",
16 | home: process.env[(os.platform() === "win32") ? "USERPROFILE" : "HOME"],
17 | EOL: os.EOL
18 | };
19 |
20 | Object.defineProperties(environment, {
21 | isPosix: {
22 | get: function () {
23 | return !this.isWin;
24 | }
25 | },
26 | _isNinjaAvailable: {
27 | value: null,
28 | writable: true
29 | },
30 | isNinjaAvailable: {
31 | get: function() {
32 | if (this._isNinjaAvailable === null) {
33 | this._isNinjaAvailable = false;
34 | try {
35 | if (which.sync("ninja")) {
36 | this._isNinjaAvailable = true;
37 | }
38 | }
39 | catch (e) {
40 | _.noop(e);
41 | }
42 | }
43 | return this._isNinjaAvailable;
44 | }
45 | },
46 | _isMakeAvailable: {
47 | value: null,
48 | writable: true
49 | },
50 | isMakeAvailable: {
51 | get: function() {
52 | if (this._isMakeAvailable === null) {
53 | this._isMakeAvailable = false;
54 | try {
55 | if (which.sync("make")) {
56 | this._isMakeAvailable = true;
57 | }
58 | }
59 | catch (e) {
60 | _.noop(e);
61 | }
62 | }
63 | return this._isMakeAvailable;
64 | }
65 | },
66 | _isGPPAvailable: {
67 | value: null,
68 | writable: true
69 | },
70 | isGPPAvailable: {
71 | get: function() {
72 | if (this._isGPPAvailable === null) {
73 | this._isGPPAvailable = false;
74 | try {
75 | if (which.sync("g++")) {
76 | this._isGPPAvailable = true;
77 | }
78 | }
79 | catch (e) {
80 | _.noop(e);
81 | }
82 | }
83 | return this._isGPPAvailable;
84 | }
85 | },
86 | _isClangAvailable: {
87 | value: null,
88 | writable: true
89 | },
90 | isClangAvailable: {
91 | get: function() {
92 | if (this._isClangAvailable === null) {
93 | this._isClangAvailable = false;
94 | try {
95 | if (which.sync("clang++")) {
96 | this._isClangAvailable = true;
97 | }
98 | }
99 | catch (e) {
100 | _.noop(e);
101 | }
102 | }
103 | return this._isClangAvailable;
104 | }
105 | }
106 | });
--------------------------------------------------------------------------------
/cmake-tool/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var log = require("npmlog");
3 | var _ = require("lodash");
4 | var BuildSystem = require("./buildSystem");
5 | var util = require("util");
6 | var version = require("../package").version;
7 | var logLevels = ["silly", "verbose", "info", "http", "warn", "error"];
8 |
9 | var yargs = require("yargs")
10 | .usage("CMake.js " + version + "\n\nUsage: $0 [] [options]")
11 | .command("configure", "Configure CMake project")
12 | .command("print-configure", "Print the configuration command")
13 | .command("build", "Build the project (will configure first if required)")
14 | .command("print-build", "Print the build command")
15 | .command("clean", "Clean the project directory")
16 | .command("print-clean", "Print the clean command")
17 | .command("reconfigure", "Clean the project directory then configure the project")
18 | .command("rebuild", "Clean the project directory then build the project")
19 | .command("compile", "Build the project, and if build fails, try a full rebuild")
20 | .options({
21 | h: {
22 | alias: "help",
23 | demand: false,
24 | describe: "show this screen",
25 | type: "boolean"
26 | },
27 | l: {
28 | alias: "log-level",
29 | demand: false,
30 | describe: "set log level (" + logLevels.join(", ") + "), default is info",
31 | type: "string"
32 | },
33 | d: {
34 | alias: "directory",
35 | demand: false,
36 | describe: "specify CMake project's directory (where CMakeLists.txt located)",
37 | type: "string"
38 | },
39 | D: {
40 | alias: "debug",
41 | demand: false,
42 | describe: "build debug configuration",
43 | type: "boolean"
44 | },
45 | c: {
46 | alias: "cmake-path",
47 | demand: false,
48 | describe: "path of CMake executable",
49 | type: "string"
50 | },
51 | m: {
52 | alias: "prefer-make",
53 | demand: false,
54 | describe: "use Unix Makefiles even if Ninja is available (Posix)",
55 | type: "boolean"
56 | },
57 | x: {
58 | alias: "prefer-xcode",
59 | demand: false,
60 | describe: "use Xcode instead of Unix Makefiles",
61 | type: "boolean"
62 | },
63 | g: {
64 | alias: "prefer-gnu",
65 | demand: false,
66 | describe: "use GNU compiler instead of default CMake compiler, if available (Posix)",
67 | type: "boolean"
68 | },
69 | G: {
70 | alias: "generator",
71 | demand: false,
72 | describe: "use specified generator",
73 | type: "string"
74 | },
75 | t: {
76 | alias: "toolset",
77 | demand: false,
78 | describe: "use specified toolset",
79 | type: "string"
80 | },
81 | A: {
82 | alias: "platform",
83 | demand: false,
84 | describe: "use specified platform name",
85 | type: "string"
86 | },
87 | T: {
88 | alias: "target",
89 | demand: false,
90 | describe: "only build the specified target",
91 | type: "string"
92 | },
93 | C: {
94 | alias: "prefer-clang",
95 | demand: false,
96 | describe: "use Clang compiler instead of default CMake compiler, if available (Posix)",
97 | type: "boolean"
98 | },
99 | cc: {
100 | demand: false,
101 | describe: "use the specified C compiler",
102 | type: "string"
103 | },
104 | cxx: {
105 | demand: false,
106 | describe: "use the specified C++ compiler",
107 | type: "string"
108 | },
109 | a: {
110 | alias: "arch",
111 | demand: false,
112 | describe: "the architecture to build in",
113 | type: "string"
114 | },
115 | "CD": {
116 | demand: false,
117 | describe: "Custom argument passed to CMake in format: -D",
118 | type: "string"
119 | },
120 | i: {
121 | alias: "silent",
122 | describe: "Prevents CMake.js to print to the stdio",
123 | type: "boolean"
124 | },
125 | O: {
126 | alias: "out",
127 | describe: "Specify the output directory to compile to, default is projectRoot/build",
128 | type: "string"
129 | }
130 | });
131 | var argv = yargs.argv;
132 |
133 | // If help, then print and exit:
134 |
135 | if (argv.h) {
136 | console.info(yargs.help());
137 | process.exit(0);
138 | }
139 |
140 | // Setup log level:
141 |
142 | if (argv.l && _.includes(logLevels, argv.l)) {
143 | log.level = argv.l;
144 | log.resume();
145 | }
146 |
147 | log.silly("CON", "argv:");
148 | log.silly("CON", util.inspect(argv));
149 |
150 | log.verbose("CON", "Parsing arguments");
151 |
152 | // Extract custom cMake options
153 | var customOptions = {};
154 | _.keys(argv).forEach(function (key) {
155 | if (argv[key] && _.startsWith(key, "CD")) {
156 | customOptions[key.substr(2)] = argv[key];
157 | }
158 | });
159 |
160 | var options = {
161 | directory: argv.directory || null,
162 | debug: argv.debug,
163 | cmakePath: argv.c || null,
164 | generator: argv.G,
165 | toolset: argv.t,
166 | platform: argv.A,
167 | target: argv.T,
168 | preferMake: argv.m,
169 | preferXcode: argv.x,
170 | preferGnu: argv.g,
171 | preferClang: argv.C,
172 | cCompilerPath: argv.cc,
173 | cppCompilerPath: argv.cxx,
174 | arch: argv.a,
175 | cMakeOptions: customOptions,
176 | silent: argv.i,
177 | out: argv.O
178 | };
179 |
180 | log.verbose("CON", "options:");
181 | log.verbose("CON", util.inspect(options));
182 |
183 | var command = _.first(argv._) || "build";
184 |
185 | log.verbose("CON", "Running command: " + command);
186 |
187 | var buildSystem = new BuildSystem(options);
188 |
189 | function ifCommand(c, f) {
190 | if (c === command) {
191 | f();
192 | return true;
193 | }
194 | return false;
195 | }
196 |
197 | function exitOnError(promise) {
198 | promise.catch(function () {
199 | process.exit(1);
200 | });
201 | }
202 |
203 | function configure() {
204 | exitOnError(buildSystem.configure());
205 | }
206 | function printConfigure() {
207 | exitOnError(buildSystem.getConfigureCommand()
208 | .then(function (command) {
209 | console.info(command);
210 | }));
211 | }
212 | function build() {
213 | exitOnError(buildSystem.build());
214 | }
215 | function printBuild() {
216 | exitOnError(buildSystem.getBuildCommand()
217 | .then(function (command) {
218 | console.info(command);
219 | }));
220 | }
221 | function clean() {
222 | exitOnError(buildSystem.clean());
223 | }
224 | function printClean() {
225 | exitOnError(buildSystem.getCleanCommand()
226 | .then(function (command) {
227 | console.info(command);
228 | }));
229 | }
230 | function reconfigure() {
231 | exitOnError(buildSystem.reconfigure());
232 | }
233 | function rebuild() {
234 | exitOnError(buildSystem.rebuild());
235 | }
236 | function compile() {
237 | exitOnError(buildSystem.compile());
238 | }
239 |
240 | var done
241 | done = done || ifCommand("configure", configure);
242 | done = done || ifCommand("print-configure", printConfigure);
243 | done = done || ifCommand("build", build);
244 | done = done || ifCommand("print-build", printBuild);
245 | done = done || ifCommand("clean", clean);
246 | done = done || ifCommand("print-clean", printClean);
247 | done = done || ifCommand("reconfigure", reconfigure);
248 | done = done || ifCommand("rebuild", rebuild);
249 | done = done || ifCommand("compile", compile);
250 |
251 | if (!done) {
252 | if (command) {
253 | log.error("COM", "Unknown command: " + command);
254 | process.exit(1);
255 | }
256 | else {
257 | build();
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/cmake-tool/node-addon.cmake:
--------------------------------------------------------------------------------
1 | if (NOT NODE_MODULE_CACHE_DIR)
2 | set(NODE_MODULE_CACHE_DIR "${CMAKE_BINARY_DIR}")
3 | endif()
4 |
5 | if (NOT NODE_ADDON_API)
6 | set(NODE_ADDON_API 1.7.1)
7 | endif()
8 |
9 | if (NOT NODE_NAN_API)
10 | set(NODE_NAN_API 2.14.1)
11 | endif()
12 |
13 |
14 | if (WIN32)
15 | if (CMAKE_CL_64)
16 | set(ARCHSUFFIX "win32-x64")
17 | else()
18 | set(ARCHSUFFIX "win32-x86")
19 | endif()
20 | else()
21 | set(ARCHSUFFIX "linux-x64")
22 | endif()
23 |
24 | function(_node_module_download _URL _FILE)
25 | get_filename_component(_DIR "${_FILE}" DIRECTORY)
26 | file(MAKE_DIRECTORY "${_DIR}")
27 | message(STATUS "[Node.js] Downloading ${_URL}...")
28 | file(DOWNLOAD "${_URL}" "${_FILE}" STATUS _STATUS TLS_VERIFY ON)
29 | list(GET _STATUS 0 _STATUS_CODE)
30 | if(NOT _STATUS_CODE EQUAL 0)
31 | list(GET _STATUS 1 _STATUS_MESSAGE)
32 | message(FATAL_ERROR "[Node.js] Failed to download ${_URL}: ${_STATUS_MESSAGE}")
33 | endif()
34 | endfunction()
35 |
36 | function(_node_module_unpack_tar_gz _URL _PATH _DEST)
37 | string(RANDOM LENGTH 32 _TMP)
38 | set(_TMP "${CMAKE_BINARY_DIR}/${_TMP}")
39 | _node_module_download("${_URL}" "${_TMP}.tar.gz")
40 | file(REMOVE_RECURSE "${_DEST}" "${_TMP}")
41 | file(MAKE_DIRECTORY "${_TMP}")
42 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${_TMP}.tar.gz"
43 | WORKING_DIRECTORY "${_TMP}"
44 | RESULT_VARIABLE _STATUS_CODE
45 | OUTPUT_VARIABLE _STATUS_MESSAGE
46 | ERROR_VARIABLE _STATUS_MESSAGE)
47 | if(NOT _STATUS_CODE EQUAL 0)
48 | message(FATAL_ERROR "[Node.js] Failed to unpack ${_URL}: ${_STATUS_MESSAGE}")
49 | endif()
50 | get_filename_component(_DIR "${_DEST}" DIRECTORY)
51 | file(MAKE_DIRECTORY "${_DIR}")
52 | file(RENAME "${_TMP}/${_PATH}" "${_DEST}")
53 | file(REMOVE_RECURSE "${_TMP}" "${_TMP}.tar.gz")
54 | endfunction()
55 |
56 |
57 | function(add_node_module NAME)
58 | cmake_parse_arguments("" "" "ADDON_VERSION;NAN_VERSION;CACHE_DIR;SUFFIX" "NODE_ABIS" ${ARGN})
59 | if (NOT _CACHE_DIR)
60 | set(_CACHE_DIR "${NODE_MODULE_CACHE_DIR}")
61 | endif()
62 | if (NOT _ADDON_VERSION)
63 | set(_ADDON_VERSION "${NODE_ADDON_API}")
64 | endif()
65 | if (NOT _SUFFIX)
66 | if (WIN32)
67 | set(_SUFFIX ".dll")
68 | else()
69 | set(_SUFFIX ".so")
70 | endif()
71 | endif()
72 |
73 | if(NOT _NODE_ABIS)
74 | message(FATAL_ERROR "No ABIs specified")
75 | endif()
76 |
77 | # Create master target
78 | add_library(${NAME} INTERFACE)
79 |
80 | # Obtain a list of current Node versions and retrieves the latest version per ABI
81 | if(NOT EXISTS "${_CACHE_DIR}/node/index.tab")
82 | _node_module_download(
83 | "Node.js version list"
84 | "https://nodejs.org/dist/index.tab"
85 | "${_CACHE_DIR}/node/index.tab"
86 | )
87 | endif()
88 |
89 | file(STRINGS "${_CACHE_DIR}/node/index.tab" _INDEX_FILE)
90 | list(REMOVE_AT _INDEX_FILE 0)
91 |
92 | set(_ABIS)
93 | foreach(_LINE IN LISTS _INDEX_FILE)
94 | string(REGEX MATCHALL "[^\t]*\t" _COLUMNS "${_LINE}")
95 | list(GET _COLUMNS 8 _ABI)
96 | string(STRIP "${_ABI}" _ABI)
97 | if(NOT DEFINED _NODE_ABI_${_ABI}_VERSION)
98 | list(APPEND _ABIS ${_ABI})
99 | list(GET _COLUMNS 0 _VERSION)
100 | string(STRIP "${_VERSION}" _NODE_ABI_${_ABI}_VERSION)
101 | endif()
102 | endforeach()
103 |
104 | # Install Nan
105 | if(_NAN_VERSION AND NOT EXISTS "${_CACHE_DIR}/nan/${_NAN_VERSION}/nan.h")
106 | _node_module_unpack_tar_gz(
107 | "Nan ${_NAN_VERSION}"
108 | "https://registry.npmjs.org/nan/-/nan-${_NAN_VERSION}.tgz"
109 | "package"
110 | "${_CACHE_DIR}/nan/${_NAN_VERSION}"
111 | )
112 | endif()
113 |
114 | # Install addon api
115 | if(_ADDON_VERSION AND NOT EXISTS "${_CACHE_DIR}/node-addon-api/${_ADDON_VERSION}/napi.h")
116 | _node_module_unpack_tar_gz(
117 | "https://registry.npmjs.org/node-addon-api/-/node-addon-api-${_ADDON_VERSION}.tgz"
118 | "package"
119 | "${_CACHE_DIR}/node-addon-api/${_ADDON_VERSION}"
120 | )
121 | endif()
122 |
123 | # Generate a target for every ABI
124 | set(_TARGETS)
125 | foreach(_ABI IN LISTS _NODE_ABIS)
126 | set(_NODE_VERSION ${_NODE_ABI_${_ABI}_VERSION})
127 |
128 | # Download the headers if we don't have them yet
129 | if(NOT EXISTS "${_CACHE_DIR}/node/${_NODE_VERSION}/node.h")
130 | _node_module_unpack_tar_gz(
131 | "headers for Node ${_NODE_VERSION}"
132 | "https://nodejs.org/download/release/${_NODE_VERSION}/node-${_NODE_VERSION}-headers.tar.gz"
133 | "node-${_NODE_VERSION}/include/node"
134 | "${_CACHE_DIR}/node/${_NODE_VERSION}"
135 | )
136 | endif()
137 |
138 | if (WIN32)
139 | if (CMAKE_CL_64)
140 | set(LIB_DIST_URL "https://nodejs.org/dist/${_NODE_VERSION}/win-x64/node.lib")
141 | set(LIB_DIST "${_CACHE_DIR}/node/${_NODE_VERSION}/win-x64/node.lib")
142 | else()
143 | set(LIB_DIST_URL "https://nodejs.org/dist/${_NODE_VERSION}/win-x86/node.lib")
144 | set(LIB_DIST "${_CACHE_DIR}/node/${_NODE_VERSION}/win-x86/node.lib")
145 | endif()
146 |
147 | if(NOT EXISTS "${LIB_DIST}")
148 | _node_module_download(
149 | "${LIB_DIST_URL}"
150 | "${LIB_DIST}"
151 | )
152 | endif()
153 | endif()
154 |
155 | # Generate the library
156 | set(_TARGET "${NAME}-${ARCHSUFFIX}.abi-${_ABI}")
157 | add_library(${_TARGET} SHARED "${_CACHE_DIR}/empty.cpp")
158 | list(APPEND _TARGETS "${_TARGET}")
159 |
160 | # C identifiers can only contain certain characters (e.g. no dashes)
161 | string(REGEX REPLACE "[^a-z0-9]+" "_" NAME_IDENTIFIER "${NAME}")
162 |
163 | set_target_properties(${_TARGET} PROPERTIES
164 | OUTPUT_NAME "${_TARGET}"
165 | SOURCES "${DELAY_LOAD_SRC}" # Removes the fake empty.cpp again
166 | PREFIX ""
167 | SUFFIX "${_SUFFIX}"
168 | MACOSX_RPATH ON
169 | C_VISIBILITY_PRESET hidden
170 | CXX_VISIBILITY_PRESET hidden
171 | POSITION_INDEPENDENT_CODE TRUE
172 | )
173 |
174 | target_compile_definitions(${_TARGET} PRIVATE
175 | "MODULE_NAME=${NAME_IDENTIFIER}"
176 | "BUILDING_NODE_EXTENSION"
177 | "_LARGEFILE_SOURCE"
178 | "_FILE_OFFSET_BITS=64"
179 | )
180 |
181 | target_include_directories(${_TARGET} SYSTEM PRIVATE
182 | "${_CACHE_DIR}/node/${_NODE_VERSION}"
183 | )
184 |
185 | if(_NAN_VERSION)
186 | # target_compile_options(${_TARGET} PRIVATE -std=c++11)
187 | target_include_directories(${_TARGET} SYSTEM PRIVATE
188 | "${_CACHE_DIR}/nan/${_NAN_VERSION}"
189 | )
190 | endif()
191 |
192 | if(_ADDON_VERSION)
193 | # target_compile_options(${_TARGET} PRIVATE -std=c++11)
194 | target_include_directories(${_TARGET} SYSTEM PRIVATE
195 | "${_CACHE_DIR}/node-addon-api/${_ADDON_VERSION}"
196 | )
197 | endif()
198 |
199 | target_link_libraries(${_TARGET} PRIVATE ${NAME})
200 | if (WIN32)
201 | target_link_libraries(${_TARGET} PRIVATE ${LIB_DIST})
202 | endif()
203 |
204 | if(APPLE)
205 | # Ensures that linked symbols are loaded when the module is loaded instead of causing
206 | # unresolved symbol errors later during runtime.
207 | set_target_properties(${_TARGET} PROPERTIES
208 | LINK_FLAGS "-undefined dynamic_lookup -bind_at_load"
209 | )
210 | target_compile_definitions(${_TARGET} PRIVATE
211 | "_DARWIN_USE_64_BIT_INODE=1"
212 | )
213 | else()
214 | if (MSVC)
215 | set_target_properties(${_TARGET} PROPERTIES COMPILE_FLAGS "/EHsc")
216 | set_target_properties(${_TARGET} PROPERTIES LINK_FLAGS "/DELAYLOAD:node.exe /DELAYLOAD:iojs.exe")
217 | else()
218 | # Ensures that linked symbols are loaded when the module is loaded instead of causing
219 | # unresolved symbol errors later during runtime.
220 | set_target_properties(${_TARGET} PROPERTIES
221 | LINK_FLAGS "-z now"
222 | )
223 | endif()
224 | endif()
225 | endforeach()
226 |
227 | # Add a target that builds all Node ABIs.
228 | add_custom_target("${NAME}.all")
229 | add_dependencies("${NAME}.all" ${_TARGETS})
230 |
231 | # Add a variable that allows users to iterate over all of the generated/dependendent targets.
232 | set("${NAME}::abis" "${_ABIS}" PARENT_SCOPE)
233 | set("${NAME}::targets" "${_TARGETS}" PARENT_SCOPE)
234 | endfunction()
235 |
--------------------------------------------------------------------------------
/cmake-tool/processHelpers.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let Promise = require("bluebird");
3 | let splitargs = require("splitargs");
4 | let _ = require("lodash");
5 | let spawn = require("child_process").spawn;
6 | let exec = require("child_process").exec;
7 |
8 | let processHelpers = {
9 | run: function (command, options) {
10 | options = _.defaults(options, {silent: false});
11 | return new Promise(function (resolve, reject) {
12 | let args = splitargs(command);
13 | let name = args[0];
14 | args.splice(0, 1);
15 | let child = spawn(name, args, {stdio: options.silent ? "ignore" : "inherit"});
16 | let ended = false;
17 | child.on("error", function (e) {
18 | if (!ended) {
19 | reject(e);
20 | ended = true;
21 | }
22 | });
23 | child.on("exit", function (code, signal) {
24 | if (!ended) {
25 | if (code === 0) {
26 | resolve();
27 | }
28 | else {
29 | reject(new Error("Process terminated: " + code || signal));
30 | }
31 | ended = true;
32 | }
33 | });
34 | });
35 | },
36 | exec: function(command) {
37 | return new Promise(function (resolve, reject) {
38 | exec(command, function (err, stdout, stderr) {
39 | if (err) {
40 | reject(new Error(err.message + "\n" + (stdout || stderr)));
41 | }
42 | else {
43 | resolve(stdout);
44 | }
45 | });
46 | });
47 | }
48 | };
49 |
50 | module.exports = processHelpers;
51 |
--------------------------------------------------------------------------------
/cmake-tool/targetOptions.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | let environment = require("./environment");
4 | let _ = require("lodash");
5 |
6 | function TargetOptions(options) {
7 | this.options = options || {};
8 | }
9 |
10 | Object.defineProperties(TargetOptions.prototype, {
11 | arch: {
12 | get: function () {
13 | return this.options.arch || environment.arch;
14 | }
15 | },
16 | isX86: {
17 | get: function () {
18 | return this.arch === "ia32";
19 | }
20 | },
21 | isX64: {
22 | get: function () {
23 | return this.arch === "x64";
24 | }
25 | },
26 | isArm: {
27 | get: function () {
28 | return this.arch === "arm";
29 | }
30 | }
31 | });
32 |
33 | module.exports = TargetOptions;
--------------------------------------------------------------------------------
/cmake-tool/toolset.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let _ = require("lodash");
3 | let TargetOptions = require("./targetOptions");
4 | let environment = require("./environment");
5 | let assert = require("assert");
6 | let vsDetect = require("./vsDetect");
7 | let path = require("path");
8 | let CMLog = require("./cmLog");
9 | let processHelpers = require("./processHelpers");
10 |
11 | function Toolset(options) {
12 | this.options = options || {};
13 | this.targetOptions = new TargetOptions(this.options);
14 | this.generator = options.generator;
15 | this.toolset = options.toolset;
16 | this.platform = options.platform;
17 | this.target = options.target;
18 | this.cCompilerPath = options.cCompilerPath;
19 | this.cppCompilerPath = options.cppCompilerPath;
20 | this.compilerFlags = [];
21 | this.linkerFlags = [];
22 | this.makePath = null;
23 | this.log = new CMLog(this.options);
24 | this._initialized = false;
25 | }
26 |
27 | Toolset.prototype.initialize = async function (install) {
28 | if (!this._initialized) {
29 | if (environment.isWin) {
30 | await this.initializeWin(install);
31 | }
32 | else {
33 | this.initializePosix(install);
34 | }
35 | this._initialized = true;
36 | }
37 | };
38 |
39 | Toolset.prototype.initializePosix = function (install) {
40 | if (!this.cCompilerPath || !this.cppCompilerPath) {
41 | // 1: Compiler
42 | if (!environment.isGPPAvailable && !environment.isClangAvailable) {
43 | if (environment.isOSX) {
44 | throw new Error("C++ Compiler toolset is not available. Install Xcode Commandline Tools from Apple Dev Center, or install Clang with homebrew by invoking: 'brew install llvm --with-clang --with-asan'.");
45 | }
46 | else {
47 | throw new Error("C++ Compiler toolset is not available. Install proper compiler toolset with your package manager, eg. 'sudo apt-get install g++'.");
48 | }
49 | }
50 |
51 | if (this.options.preferClang && environment.isClangAvailable) {
52 | if (install) {
53 | this.log.info("TOOL", "Using clang++ compiler, because preferClang option is set, and clang++ is available.");
54 | }
55 | this.cppCompilerPath = this.cppCompilerPath || "clang++";
56 | this.cCompilerPath = this.cCompilerPath || "clang";
57 | }
58 | else if (this.options.preferGnu && environment.isGPPAvailable) {
59 | if (install) {
60 | this.log.info("TOOL", "Using g++ compiler, because preferGnu option is set, and g++ is available.");
61 | }
62 | this.cppCompilerPath = this.cppCompilerPath || "g++";
63 | this.cCompilerPath = this.cCompilerPath || "gcc";
64 | }
65 | }
66 | // if it's already set because of options...
67 | if (this.generator) {
68 | if (install) {
69 | this.log.info("TOOL", "Using " + this.options.generator + " generator, as specified from commandline.");
70 | }
71 | }
72 | // 2: Generator
73 | else if (environment.isOSX) {
74 | if (this.options.preferXcode) {
75 | if (install) {
76 | this.log.info("TOOL", "Using Xcode generator, because preferXcode option is set.");
77 | }
78 | this.generator = "Xcode";
79 | }
80 | else if (this.options.preferMake && environment.isMakeAvailable) {
81 | if (install) {
82 | this.log.info("TOOL", "Using Unix Makefiles generator, because preferMake option is set, and make is available.");
83 | }
84 | this.generator = "Unix Makefiles";
85 | }
86 | else if (environment.isNinjaAvailable) {
87 | if (install) {
88 | this.log.info("TOOL", "Using Ninja generator, because ninja is available.");
89 | }
90 | this.generator = "Ninja";
91 | }
92 | else {
93 | if (install) {
94 | this.log.info("TOOL", "Using Unix Makefiles generator.");
95 | }
96 | this.generator = "Unix Makefiles";
97 | }
98 | }
99 | else {
100 | if (this.options.preferMake && environment.isMakeAvailable) {
101 | if (install) {
102 | this.log.info("TOOL", "Using Unix Makefiles generator, because preferMake option is set, and make is available.");
103 | }
104 | this.generator = "Unix Makefiles";
105 | }
106 | else if (environment.isNinjaAvailable) {
107 | if (install) {
108 | this.log.info("TOOL", "Using Ninja generator, because ninja is available.");
109 | }
110 | this.generator = "Ninja";
111 | }
112 | else {
113 | if (install) {
114 | this.log.info("TOOL", "Using Unix Makefiles generator.");
115 | }
116 | this.generator = "Unix Makefiles";
117 | }
118 | }
119 |
120 | // 3: Flags
121 | if (environment.isOSX) {
122 | if (install) {
123 | this.log.verbose("TOOL", "Setting default OSX compiler flags.");
124 | }
125 |
126 | this.compilerFlags.push("-D_DARWIN_USE_64_BIT_INODE=1");
127 | this.compilerFlags.push("-D_LARGEFILE_SOURCE");
128 | this.compilerFlags.push("-D_FILE_OFFSET_BITS=64");
129 | this.compilerFlags.push("-DBUILDING_NODE_EXTENSION");
130 | this.linkerFlags.push("-undefined dynamic_lookup");
131 | }
132 |
133 | // 4: Build target
134 | if (this.options.target) {
135 | this.log.info("TOOL", "Building only the " + this.options.target + " target, as specified from the command line.");
136 | }
137 | };
138 |
139 | Toolset.prototype.initializeWin = async function (install) {
140 | // Visual Studio:
141 | // if it's already set because of options...
142 | if (this.generator) {
143 | if (install) {
144 | this.log.info("TOOL", "Using " + this.options.generator + " generator, as specified from commandline.");
145 | }
146 |
147 | this.linkerFlags.push("/DELAYLOAD:NODE.EXE");
148 |
149 | if (this.targetOptions.isX86) {
150 | if (install) {
151 | this.log.verbose("TOOL", "Setting SAFESEH:NO linker flag.");
152 | }
153 | this.linkerFlags.push("/SAFESEH:NO");
154 | }
155 | return;
156 | }
157 | let topVS = await this._getTopSupportedVisualStudioGenerator();
158 | if (topVS) {
159 | if (install) {
160 | this.log.info("TOOL", `Using ${topVS} generator.`);
161 | }
162 | this.generator = topVS;
163 |
164 | this.linkerFlags.push("/DELAYLOAD:NODE.EXE");
165 |
166 | if (this.targetOptions.isX86) {
167 | if (install) {
168 | this.log.verbose("TOOL", "Setting SAFESEH:NO linker flag.");
169 | }
170 | this.linkerFlags.push("/SAFESEH:NO");
171 | }
172 |
173 | // The CMake Visual Studio Generator does not support the Win64 or ARM suffix on
174 | // the generator name. Instead the generator platform must be set explicitly via
175 | // the platform parameter
176 | if (!this.platform && this.generator.startsWith("Visual Studio 16")) {
177 | switch(this.targetOptions.arch) {
178 | case "ia32":
179 | this.platform = "Win32";
180 | break;
181 | case "x64":
182 | this.platform = "x64";
183 | break;
184 | case "arm":
185 | this.platform = "ARM";
186 | break;
187 | case "arm64":
188 | this.platform = "ARM64";
189 | break;
190 | default:
191 | this.log.warn("TOOL", "Unknown NodeJS architecture: " + this.targetOptions.arch);
192 | break;
193 | }
194 | }
195 | }
196 | else {
197 | throw new Error("There is no Visual C++ compiler installed. Install Visual C++ Build Toolset or Visual Studio.");
198 | }
199 | };
200 |
201 | Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () {
202 | let CMake = require("./cMake");
203 | assert(environment.isWin);
204 |
205 | let vswhereVersion = await this._getVersionFromVSWhere();
206 |
207 | let list = await CMake.getGenerators(this.options);
208 | let maxVer = 0;
209 | let result = null;
210 | for (let gen of list) {
211 | let found = /^visual studio (\d+)/i.exec(gen);
212 | if (!found) {
213 | continue;
214 | }
215 |
216 | let ver = parseInt(found[1]);
217 | if (ver <= maxVer) {
218 | continue;
219 | }
220 |
221 | // unlike previous versions "Visual Studio 16 2019" doesn't end with arch name
222 | const isAboveVS16 = ver >= 16;
223 | if (!isAboveVS16) {
224 | const is64Bit = gen.endsWith("Win64");
225 | if ((this.targetOptions.isX86 && is64Bit) || (this.targetOptions.isX64 && !is64Bit)) {
226 | continue;
227 | }
228 | }
229 |
230 | if (ver === vswhereVersion || (await vsDetect.isInstalled(ver + ".0"))) {
231 | result = gen;
232 | maxVer = ver;
233 | }
234 | }
235 | return result;
236 | };
237 |
238 | Toolset.prototype._getVersionFromVSWhere = async function () {
239 | let programFilesPath = _.get(process.env, "ProgramFiles(x86)", _.get(process.env, "ProgramFiles"));
240 | let vswhereCommand = path.resolve(programFilesPath, "Microsoft Visual Studio", "Installer", "vswhere.exe");
241 | let vswhereOutput = null;
242 |
243 | try {
244 | this.log.verbose("TOOL", `Looking for vswhere.exe at '${vswhereCommand}'.`);
245 | vswhereOutput = await processHelpers.exec(`"${vswhereCommand}" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationVersion`);
246 | }
247 | catch (e) {
248 | this.log.verbose("TOOL", "Could not find vswhere.exe (VS installation is probably older than 15.2).");
249 | return null;
250 | }
251 |
252 | if (!vswhereOutput) {
253 | return null;
254 | }
255 |
256 | let version = vswhereOutput.trim();
257 | version = version.substring(0, version.indexOf("."));
258 |
259 | return Number(version);
260 | };
261 |
262 | module.exports = Toolset;
263 |
--------------------------------------------------------------------------------
/cmake-tool/vsDetect.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | let processHelpers = require("./processHelpers");
3 | let _ = require("lodash");
4 | let path = require("path");
5 |
6 | let vsDetect = {
7 | isInstalled: async function (version) {
8 | let vsInstalled = await this._isVSInstalled(version);
9 | let vsvNextInstalled = await this._isVSvNextInstalled(version);
10 | let buildToolsInstalled = await this._isBuildToolsInstalled(version);
11 | let foundByVSWhere = await this._isFoundByVSWhere(version);
12 |
13 | return vsInstalled || vsvNextInstalled || buildToolsInstalled || foundByVSWhere;
14 | },
15 |
16 | _isFoundByVSWhere: async function (version) {
17 | // TODO: with auto download
18 | /*
19 | let mainVer = version.split(".")[0];
20 | let command = path.resolve("vswhere.exe");
21 | try {
22 | let stdout = yield processHelpers.exec(command, ["-version", version]);
23 | return stdout && stdout.indexOf("installationVersion: " + mainVer) > 0;
24 | }
25 | catch (e) {
26 | _.noop(e);
27 | }
28 | */
29 | return false;
30 | },
31 |
32 | _isBuildToolsInstalled: async function (version) {
33 | let mainVer = version.split(".")[0];
34 | let key;
35 | let testPhrase;
36 | if (Number(mainVer) >= 15) {
37 | key = "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VS.windows_toolscore,v" + mainVer;
38 | testPhrase = "Version";
39 | }
40 | else {
41 | key = "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VS.VisualCppBuildTools_x86_enu,v" + mainVer;
42 | testPhrase = "Visual C++";
43 | }
44 | let command = "reg query \"" + key + "\"";
45 | try {
46 | let stdout = await processHelpers.exec(command);
47 | return stdout && stdout.indexOf(testPhrase) > 0;
48 | }
49 | catch (e) {
50 | _.noop(e);
51 | }
52 | return false;
53 | },
54 |
55 | _isVSInstalled: async function (version) {
56 | // On x64 this will look for x64 keys only, but if VS and compilers installed properly,
57 | // it will write it's keys to 64 bit registry as well.
58 | let command = "reg query \"HKLM\\Software\\Microsoft\\VisualStudio\\" + version + "\"";
59 | try {
60 | let stdout = await processHelpers.exec(command);
61 | if (stdout) {
62 | let lines = stdout.split("\r\n").filter(function (line) {
63 | return line.length > 10;
64 | });
65 | if (lines.length >= 4) {
66 | return true;
67 | }
68 | }
69 | }
70 | catch (e) {
71 | _.noop(e);
72 | }
73 | return false;
74 | },
75 |
76 | _isVSvNextInstalled: async function (version) {
77 | let mainVer = version.split(".")[0];
78 | let command = "reg query \"HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VisualStudio.MinShell.Msi,v" + mainVer + "\"";
79 | try {
80 | let stdout = await processHelpers.exec(command);
81 | if (stdout) {
82 | let lines = stdout.split("\r\n").filter(function (line) {
83 | return line.length > 10;
84 | });
85 | if (lines.length >= 3) {
86 | return true;
87 | }
88 | }
89 | }
90 | catch (e) {
91 | _.noop(e);
92 | }
93 | return false;
94 | }
95 | };
96 |
97 | module.exports = vsDetect;
98 |
--------------------------------------------------------------------------------
/ffplay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevevista/electron-ffplay/b41cecdedbe047a35b3a4d09ef7b2bf09a21f53a/ffplay.png
--------------------------------------------------------------------------------
/licenses/FFmpeg.md:
--------------------------------------------------------------------------------
1 | # License
2 |
3 | Most files in FFmpeg are under the GNU Lesser General Public License version 2.1
4 | or later (LGPL v2.1+). Read the file `COPYING.LGPLv2.1` for details. Some other
5 | files have MIT/X11/BSD-style licenses. In combination the LGPL v2.1+ applies to
6 | FFmpeg.
7 |
8 | Some optional parts of FFmpeg are licensed under the GNU General Public License
9 | version 2 or later (GPL v2+). See the file `COPYING.GPLv2` for details. None of
10 | these parts are used by default, you have to explicitly pass `--enable-gpl` to
11 | configure to activate them. In this case, FFmpeg's license changes to GPL v2+.
12 |
13 | Specifically, the GPL parts of FFmpeg are:
14 |
15 | - libpostproc
16 | - optional x86 optimization in the files
17 | - `libavcodec/x86/flac_dsp_gpl.asm`
18 | - `libavcodec/x86/idct_mmx.c`
19 | - `libavfilter/x86/vf_removegrain.asm`
20 | - the following building and testing tools
21 | - `compat/solaris/make_sunver.pl`
22 | - `doc/t2h.pm`
23 | - `doc/texi2pod.pl`
24 | - `libswresample/tests/swresample.c`
25 | - `tests/checkasm/*`
26 | - `tests/tiny_ssim.c`
27 | - the following filters in libavfilter:
28 | - `signature_lookup.c`
29 | - `vf_blackframe.c`
30 | - `vf_boxblur.c`
31 | - `vf_colormatrix.c`
32 | - `vf_cover_rect.c`
33 | - `vf_cropdetect.c`
34 | - `vf_delogo.c`
35 | - `vf_eq.c`
36 | - `vf_find_rect.c`
37 | - `vf_fspp.c`
38 | - `vf_geq.c`
39 | - `vf_histeq.c`
40 | - `vf_hqdn3d.c`
41 | - `vf_kerndeint.c`
42 | - `vf_lensfun.c` (GPL version 3 or later)
43 | - `vf_mcdeint.c`
44 | - `vf_mpdecimate.c`
45 | - `vf_nnedi.c`
46 | - `vf_owdenoise.c`
47 | - `vf_perspective.c`
48 | - `vf_phase.c`
49 | - `vf_pp.c`
50 | - `vf_pp7.c`
51 | - `vf_pullup.c`
52 | - `vf_repeatfields.c`
53 | - `vf_sab.c`
54 | - `vf_signature.c`
55 | - `vf_smartblur.c`
56 | - `vf_spp.c`
57 | - `vf_stereo3d.c`
58 | - `vf_super2xsai.c`
59 | - `vf_tinterlace.c`
60 | - `vf_uspp.c`
61 | - `vf_vaguedenoiser.c`
62 | - `vsrc_mptestsrc.c`
63 |
64 | Should you, for whatever reason, prefer to use version 3 of the (L)GPL, then
65 | the configure parameter `--enable-version3` will activate this licensing option
66 | for you. Read the file `COPYING.LGPLv3` or, if you have enabled GPL parts,
67 | `COPYING.GPLv3` to learn the exact legal terms that apply in this case.
68 |
69 | There are a handful of files under other licensing terms, namely:
70 |
71 | * The files `libavcodec/jfdctfst.c`, `libavcodec/jfdctint_template.c` and
72 | `libavcodec/jrevdct.c` are taken from libjpeg, see the top of the files for
73 | licensing details. Specifically note that you must credit the IJG in the
74 | documentation accompanying your program if you only distribute executables.
75 | You must also indicate any changes including additions and deletions to
76 | those three files in the documentation.
77 | * `tests/reference.pnm` is under the expat license.
78 |
79 |
80 | ## External libraries
81 |
82 | FFmpeg can be combined with a number of external libraries, which sometimes
83 | affect the licensing of binaries resulting from the combination.
84 |
85 | ### Compatible libraries
86 |
87 | The following libraries are under GPL version 2:
88 | - avisynth
89 | - frei0r
90 | - libcdio
91 | - libdavs2
92 | - librubberband
93 | - libvidstab
94 | - libx264
95 | - libx265
96 | - libxavs
97 | - libxavs2
98 | - libxvid
99 |
100 | When combining them with FFmpeg, FFmpeg needs to be licensed as GPL as well by
101 | passing `--enable-gpl` to configure.
102 |
103 | The following libraries are under LGPL version 3:
104 | - gmp
105 | - libaribb24
106 | - liblensfun
107 |
108 | When combining them with FFmpeg, use the configure option `--enable-version3` to
109 | upgrade FFmpeg to the LGPL v3.
110 |
111 | The VMAF, mbedTLS, RK MPI, OpenCORE and VisualOn libraries are under the Apache License
112 | 2.0. That license is incompatible with the LGPL v2.1 and the GPL v2, but not with
113 | version 3 of those licenses. So to combine these libraries with FFmpeg, the
114 | license version needs to be upgraded by passing `--enable-version3` to configure.
115 |
116 | The smbclient library is under the GPL v3, to combine it with FFmpeg,
117 | the options `--enable-gpl` and `--enable-version3` have to be passed to
118 | configure to upgrade FFmpeg to the GPL v3.
119 |
120 | ### Incompatible libraries
121 |
122 | There are certain libraries you can combine with FFmpeg whose licenses are not
123 | compatible with the GPL and/or the LGPL. If you wish to enable these
124 | libraries, even in circumstances that their license may be incompatible, pass
125 | `--enable-nonfree` to configure. This will cause the resulting binary to be
126 | unredistributable.
127 |
128 | The Fraunhofer FDK AAC and OpenSSL libraries are under licenses which are
129 | incompatible with the GPLv2 and v3. To the best of our knowledge, they are
130 | compatible with the LGPL.
131 |
--------------------------------------------------------------------------------
/node-ffplay/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | # cmake -G "Visual Studio 15" -A x64 .. -DFFMPEG_PREBUILD_DIR=...
3 | # cmake --build . --config Release
4 |
5 | cmake_minimum_required(VERSION 3.9)
6 |
7 | project(node-ffplay)
8 |
9 | set(THIRD_PARTY_PREBUILT ${THIRD_PARTY_DIR}/prebuilt)
10 |
11 | if (NOT FFMPEG_PREBUILD_DIR)
12 | set(FFMPEG_PREBUILD_DIR ${THIRD_PARTY_PREBUILT})
13 | endif()
14 |
15 | set(FFMPEG_INCLUDE_PATH ${FFMPEG_PREBUILD_DIR}/include)
16 | set(FFMPEG_LIB_PATH ${FFMPEG_PREBUILD_DIR}/lib)
17 |
18 | set(THIRD_INC_PATH ${THIRD_PARTY_PREBUILT}/include)
19 | if (CMAKE_CL_64)
20 | set(THIRD_LIB_PATH ${THIRD_PARTY_PREBUILT}/lib/x64)
21 | else()
22 | set(THIRD_LIB_PATH ${THIRD_PARTY_PREBUILT}/lib/x86)
23 | endif()
24 |
25 | include(../cmake-tool/node-addon.cmake)
26 |
27 | # build nodejs module
28 | add_node_module(node-ffplay
29 | CACHE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../node_cache"
30 | SUFFIX ".dll"
31 | NODE_ABIS ${NODE_ABI})
32 |
33 | list(GET node-ffplay::targets 0 _TARGET)
34 |
35 | target_sources(node-ffplay INTERFACE
36 | ${CMAKE_CURRENT_SOURCE_DIR}/src/wrap.cc
37 | ${CMAKE_CURRENT_SOURCE_DIR}/src/player.cc
38 | )
39 |
40 | if(MSVC)
41 | target_compile_options(node-ffplay INTERFACE /W3 /WX-
42 | /wd"4005"
43 | /wd"4018"
44 | /wd"4047"
45 | /wd"4090"
46 | /wd"4101"
47 | /wd"4146"
48 | /wd"4229"
49 | /wd"4244"
50 | /wd"4267"
51 | /wd"4305"
52 | /wd"4334"
53 | /wd"4819"
54 | /wd"4996")
55 | endif()
56 |
57 | target_link_directories(node-ffplay INTERFACE
58 | ${FFMPEG_LIB_PATH}
59 | ${THIRD_LIB_PATH})
60 |
61 | target_include_directories(node-ffplay INTERFACE
62 | ${FFMPEG_INCLUDE_PATH}
63 | ${THIRD_INC_PATH})
64 | target_compile_definitions(node-ffplay INTERFACE BUILD_NODEJS WIN32 _WINDOWS _USE_MATH_DEFINES _CRT_SECURE_NO_WARNINGS _WIN32_WINNT=0x0600 NDEBUG)
65 | target_compile_definitions(node-ffplay INTERFACE NAPI_CPP_EXCEPTIONS) # NAPI_DISABLE_CPP_EXCEPTIONS
66 | #target_compile_definitions(node-ffplay INTERFACE BUILD_WITH_AUDIO_FILTER) # BUILD_WITH_VIDEO_FILTER
67 |
68 | target_link_libraries(node-ffplay INTERFACE
69 | avcodec
70 | avutil
71 | avformat
72 | swresample
73 | swscale
74 | avfilter
75 | sdl2)
76 |
77 | add_custom_target(CopyRuntimeFiles ALL
78 | VERBATIM
79 | COMMAND_EXPAND_LISTS
80 | COMMAND ${CMAKE_COMMAND} -E
81 | copy_if_different
82 | $
83 | "${CMAKE_CURRENT_SOURCE_DIR}/../"
84 | )
85 |
--------------------------------------------------------------------------------
/node-ffplay/src/player.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #undef main
4 |
5 | extern "C" {
6 | #include "libavutil/opt.h"
7 | #include "libavcodec/avcodec.h"
8 | #include "libavformat/avformat.h"
9 | # include "libavfilter/avfilter.h"
10 | # include "libavfilter/buffersink.h"
11 | # include "libavfilter/buffersrc.h"
12 | }
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | using namespace std;
23 |
24 | #ifndef CONFIG_AVFILTER
25 | #undef BUILD_WITH_AUDIO_FILTER
26 | #undef BUILD_WITH_VIDEO_FILTER
27 | #endif
28 |
29 | /* no AV correction is done if too big error */
30 | #define AV_NOSYNC_THRESHOLD 10.0
31 |
32 | void ff_init();
33 |
34 | enum MediaStatus {
35 | MEDIA_STATUS_START = 1,
36 | MEDIA_STATUS_PAUSED,
37 | MEDIA_STATUS_RESUMED,
38 | MEDIA_STATUS_REWIND_END
39 | };
40 |
41 | enum {
42 | AV_SYNC_AUDIO_MASTER, /* default choice */
43 | AV_SYNC_VIDEO_MASTER,
44 | AV_SYNC_EXTERNAL_CLOCK, /* synchronize to an external clock */
45 | };
46 |
47 | #define VIDEO_PICTURE_QUEUE_SIZE 3
48 | #define SUBPICTURE_QUEUE_SIZE 16
49 | #define SAMPLE_QUEUE_SIZE 9
50 | #define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))
51 |
52 | typedef struct AudioParams {
53 | int freq{0};
54 | int channels{0};
55 | int64_t channel_layout{0};
56 | enum AVSampleFormat fmt{ AV_SAMPLE_FMT_NONE };
57 | int frame_size{0};
58 | int bytes_per_sec{0};
59 | } AudioParams;
60 |
61 |
62 | struct Clock {
63 | double pts{0}; /* clock base */
64 | double pts_drift{0}; /* clock base minus time at which we updated the clock */
65 | double last_updated{0};
66 | double speed{0};
67 | int serial{0}; /* clock is based on a packet with this serial */
68 | int paused{0};
69 | int *queue_serial{0}; /* pointer to the current packet queue serial, used for obsolete clock detection */
70 |
71 | Clock(int *queue_serial);
72 | double get_clock() const;
73 | double time_passed() const;
74 | void update();
75 | void set_clock_at(double pts, int serial, double time);
76 | void set_clock(double pts, int serial);
77 | void sync_clock_to_slave(const Clock *slave, double threshold = AV_NOSYNC_THRESHOLD);
78 | void set_clock_speed(double speed);
79 | };
80 |
81 | class PacketQueue {
82 | public:
83 | PacketQueue(int& serial);
84 | ~PacketQueue();
85 |
86 | bool has_enough_packets(const AVRational& time_base) const;
87 | int size() const { return size_; }
88 | int packetsCount() const { return nb_packets; }
89 | bool empty() const { return nb_packets == 0; }
90 |
91 | int put(AVPacket *pkt, int specified_serial = -1);
92 | int put_nullpacket(int stream_index);
93 | int get(AVPacket *pkt, int *serial);
94 | void start();
95 | void nextSerial();
96 | void abort();
97 |
98 | private:
99 | void flush();
100 | int put_private(std::unique_lock& lk, AVPacket *pkt, int specified_serial);
101 |
102 | private:
103 | std::queue> pkts_;
104 | int nb_packets{0};
105 | int size_{0};
106 | int64_t duration_{0};
107 | bool abort_request_{true};
108 | std::mutex mtx;
109 | std::condition_variable cond;
110 |
111 | int& serial_; // referenced streaming serial
112 | };
113 |
114 | /* Common struct for handling all types of decoded data and allocated render buffers. */
115 | struct Frame {
116 | AVFrame *frame{nullptr};
117 | AVSubtitle sub{0};
118 | int serial{0};
119 | double pts{0}; /* presentation timestamp for the frame */
120 | double duration{0}; /* estimated duration of the frame */
121 | int64_t pos{0}; /* byte position of the frame in the input file */
122 | int width{0};
123 | int height{0};
124 | int format{0};
125 | AVRational sar{0};
126 | int uploaded{0};
127 | };
128 |
129 | struct SimpleFrame {
130 | SimpleFrame(AVFrame *src_frame, int serial, double pts, double duration) {
131 | frame = av_frame_alloc();
132 | av_frame_move_ref(frame, src_frame);
133 | av_frame_unref(src_frame);
134 | this->serial = serial;
135 |
136 | this->pts = pts;
137 | this->duration = duration;
138 | }
139 |
140 | ~SimpleFrame() {
141 | av_frame_free(&frame);
142 | }
143 |
144 | SimpleFrame(const SimpleFrame&) = delete;
145 | SimpleFrame& operator=(const SimpleFrame&) = delete;
146 |
147 | SimpleFrame(SimpleFrame&& other) {
148 | this->frame = other.frame;
149 | this->serial = other.serial;
150 | this->pts = other.pts;
151 | this->duration = other.duration;
152 | other.frame = nullptr;
153 | }
154 |
155 | SimpleFrame& operator=(SimpleFrame&& other) {
156 | this->frame = other.frame;
157 | this->serial = other.serial;
158 | this->pts = other.pts;
159 | this->duration = other.duration;
160 | other.frame = nullptr;
161 | return *this;
162 | }
163 |
164 | AVFrame *frame{nullptr};
165 | int serial{0};
166 | double pts{0}; /* presentation timestamp for the frame */
167 | double duration{0}; /* estimated duration of the frame */
168 | };
169 |
170 | struct FrameQueue {
171 | FrameQueue(const int& serial, int max_size, bool keep_last);
172 | ~FrameQueue();
173 | void abort();
174 |
175 | int64_t lastShownPosition() const;
176 | /* return the number of undisplayed frames in the queue */
177 | int nb_remaining() const {
178 | return size - rindex_shown;
179 | }
180 |
181 | Frame *peek_readable();
182 | Frame *peek_writable();
183 | void next();
184 | void push();
185 | Frame *peek();
186 | Frame *peek_next();
187 | Frame *peek_last();
188 |
189 | Frame queue[FRAME_QUEUE_SIZE]{0};
190 | int rindex{0};
191 | int windex{0};
192 | int size{0};
193 | int rindex_shown{0};
194 | std::mutex mtx;
195 | std::condition_variable cond;
196 |
197 | private:
198 | const int& serial_;
199 | const int max_size_{0};
200 | const bool keep_last_{false};
201 | bool abort_request_{false};
202 | };
203 |
204 | using PacketGetter = std::function;
205 |
206 | class Decoder {
207 | public:
208 | Decoder(const int& serial);
209 | virtual ~Decoder();
210 |
211 | void init(AVCodecContext *avctx);
212 | void destroy();
213 |
214 | void abort();
215 | bool finished() const;
216 | int decodeFrame(PacketGetter packet_getter, AVFrame *frame, AVSubtitle *sub, int& pkt_serial);
217 |
218 | template
219 | void start(LoopFunc&& f) {
220 | abort();
221 | abort_request_ = false;
222 | tid_ = std::thread([this, f] {
223 | f(this, &this->finished_);
224 | });
225 | }
226 |
227 | bool valid() const { return !!avctx_; }
228 | const AVCodecContext* context() const { return avctx_; }
229 |
230 | int64_t start_pts{0};
231 | AVRational start_pts_tb{0};
232 | int64_t next_pts{0};
233 | AVRational next_pts_tb{0};
234 |
235 | private:
236 | std::thread tid_;
237 | bool abort_request_{true};
238 |
239 | int finished_{0};
240 | const int& serial_;
241 |
242 | AVCodecContext *avctx_{nullptr};
243 | AVPacket pending_pkt_{0};
244 | bool packet_pending_{false};
245 | };
246 |
247 |
248 | // only for test
249 | class SyncDecoder : public Decoder {
250 | public:
251 | SyncDecoder();
252 | ~SyncDecoder();
253 |
254 | int decodeBuffer(const uint8_t* data, int size, AVFrame **frame_out);
255 |
256 | static SyncDecoder* open(const char* codec_name, const AVCodecParameters *par);
257 | private:
258 | int placeHodler_{1};
259 | AVFrame *frame_{nullptr};
260 | };
261 |
262 | enum MediaCommand {
263 | MEDIA_CMD_QUIT = 1,
264 | MEDIA_CMD_PAUSE,
265 | MEDIA_CMD_VOLUMN, // arg0=0: mute arg0=1: up arg0=1-1: down
266 | MEDIA_CMD_NEXT_FRAME,
267 | MEDIA_CMD_PREV_FRAME,
268 | MEDIA_CMD_CHAPTER,
269 | MEDIA_CMD_SEEK,
270 | MEDIA_CMD_SPEED
271 | };
272 |
273 | /*
274 | MEDIA_CMD_SEEK, arg0,
275 | arg0 = 0, seek by pts
276 | arg0 = 1, seek by pts relative, arg1 = delta
277 | arg0 = 2, seek by frmae id, arg1 = int64(id)
278 |
279 | */
280 |
281 | enum SeekMethod {
282 | SEEK_METHOD_NONE,
283 | SEEK_METHOD_POS,
284 | SEEK_METHOD_BYTES,
285 | SEEK_METHOD_REWIND,
286 | SEEK_METHOD_REWIND_CONTINUE
287 | };
288 |
289 | typedef struct MediaEvent {
290 | int event;
291 | int arg0;
292 | double arg1;
293 | double arg2;
294 | } MediaEvent;
295 |
296 | class EventQueue {
297 | public:
298 | void set(MediaEvent *evt);
299 | bool get(MediaEvent *evt);
300 |
301 | private:
302 | std::queue evts_;
303 | bool quit_{false};
304 | std::mutex mtx;
305 | };
306 |
307 |
308 | struct ConverterContext {
309 | ConverterContext(AVPixelFormat fmt);
310 | ~ConverterContext();
311 |
312 | int convert(AVFrame *frame);
313 | int convert(int src_format, int src_width, int src_height, const uint8_t * const*pixels, int* pitch);
314 |
315 | struct SwsContext *convert_ctx{nullptr};
316 | uint8_t* buffer{nullptr};
317 | int buffer_size_{0};
318 |
319 | AVFrame *frame_{nullptr};
320 | const AVPixelFormat target_fmt{AV_PIX_FMT_NONE};
321 | };
322 |
323 | struct Detection_t;
324 |
325 | using OnStatus = std::function;
326 | using OnMetaInfo = std::function;
332 | using OnStatics = std::function;
333 | using OnClockUpdate = std::function;
334 | using OnIYUVDisplay = std::function;
335 | using OnAIData = std::function;
336 | using OnLog = std::function;
337 |
338 | class PlayBackContext {
339 | public:
340 | virtual ~PlayBackContext();
341 | PlayBackContext();
342 |
343 | void eventLoop(int argc, char **argv);
344 | void sendEvent(int event, int arg0, double arg1, double arg2);
345 |
346 | protected:
347 | const Clock& masterClock() const;
348 | int get_master_sync_type() const;
349 | double get_master_clock() const;
350 | void adjustExternalClockSpeed();
351 |
352 | int64_t ptsToFrameId(double pts) const;
353 | double frameIdToPts(int64_t id) const;
354 |
355 | void streamOpen();
356 | void streamClose();
357 | void streamComponentOpen(int stream_index);
358 | void streamComponentClose(int stream_index);
359 |
360 | static int decode_interrupt_cb(void *ctx);
361 |
362 | void doReadInThread();
363 |
364 | void audioOpen(int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate);
365 | static void sdl_audio_callback(void *opaque, Uint8 *stream, int len);
366 | int audio_decode_frame();
367 | int synchronize_audio(int nb_samples);
368 |
369 | void configureAudioFilters(bool force_output_format);
370 | int configure_video_filters(AVFilterGraph *graph, const char *vfilters, AVFrame *frame);
371 |
372 | void onPacketDrained();
373 |
374 | int onVideoFrameDecodedReversed(AVFrame *frame, int serial);
375 |
376 | void startVideoDecodeThread();
377 | void startAudioDecodeThread();
378 | void startSubtitleDecodeThread();
379 |
380 | void startDataDecode();
381 | void stopDataDecode();
382 | int receiveDataPacket(AVPacket *pkt, int& pkt_serial);
383 | int dealWithDataPacket(const AVPacket *pkt, const int pkt_serial);
384 |
385 | int pushPacket(AVPacket* pkt, int specified_serial = -1);
386 | void newSerial();
387 |
388 | int queuePicture(AVFrame *src_frame, double pts, double duration, int64_t pos, int serial);
389 |
390 | void refreshLoopWaitEvent(MediaEvent *event);
391 | int handleEvent(const MediaEvent& event, int& quit);
392 |
393 | int getVideoFrame(AVFrame *frame, int& pkt_serial);
394 |
395 | void video_refresh(double *remaining_time);
396 | void video_image_display();
397 |
398 | void video_refresh_rewind(double *remaining_time);
399 | double computeVideoTargetDelayReversed(const Frame *lastvp, const Frame *vp) const;
400 | double vpDurationReversed(const Frame *vp, const Frame *nextvp) const;
401 |
402 | double vp_duration(const Frame *vp, const Frame *nextvp) const;
403 | double compute_target_delay(double delay) const;
404 |
405 | void stream_toggle_pause();
406 |
407 | void videoRefreshShowStatus(int64_t& last_time) const;
408 |
409 | void sendSeekRequest(SeekMethod req, int64_t pos, int64_t rel = 0);
410 |
411 | void updateVolume(int sign, double step);
412 | void setVolume(int val);
413 | void setMute(bool v);
414 | void toggleMute();
415 | int getVolume() const;
416 | bool isMuted() const;
417 |
418 | void change_speed(double speed);
419 |
420 | void step_to_next_frame();
421 | void step_to_prev_frame();
422 | void togglePause();
423 | void seek_chapter(int incr);
424 |
425 | bool videoPacketIsAddonData(AVCodecID codec_id, const AVPacket *pkt) const;
426 |
427 | protected:
428 | //bool rewindMode() const { return speed_ < 0; }
429 | bool rewindMode() const { return rewind_; }
430 |
431 | protected:
432 | Clock audclk;
433 | Clock vidclk;
434 | Clock extclk;
435 |
436 | bool realtime_{false};
437 |
438 | AVFormatContext *ic{nullptr};
439 |
440 | // seeking & speed
441 | SeekMethod seekMethod_{SEEK_METHOD_NONE};
442 | bool rewind_{false};
443 | int64_t seek_pos{0};
444 | int64_t seek_rel{0};
445 | int read_pause_return{0};
446 | double speed_{1.0};
447 | double prev_speed_{0};
448 | bool stepping_{false};
449 |
450 | std::deque rewindBuffer_;
451 | int64_t frameRewindTarget_;
452 | int64_t rewindEofPts_{0};
453 | int64_t syncVideoPts_{-1};
454 |
455 | double max_frame_duration{0}; // maximum duration of a frame - above this, we consider the jump a timestamp discontinuity
456 | int last_video_stream{-1};
457 | int last_audio_stream{-1};
458 | int last_subtitle_stream{-1};
459 | int last_data_stream{-1};
460 |
461 | int audio_stream{-1};
462 | AVStream *audio_st{nullptr};
463 | PacketQueue audioPacketQueue_;
464 | FrameQueue sampleQueue_;
465 | Decoder audioDecoder_;
466 |
467 | int audio_hw_buf_size{0};
468 | uint8_t *audio_buf{nullptr};
469 | uint8_t *audio_buf1{nullptr};
470 | unsigned int audio_buf_size{0}; /* in bytes */
471 | unsigned int audio_buf1_size{0};
472 | int audio_buf_index{0}; /* in bytes */
473 | int audio_write_buf_size{0};
474 | bool muted_{false};
475 | struct AudioParams audio_src;
476 |
477 | struct SwrContext *swr_ctx{nullptr};
478 |
479 | double audio_clock{0};
480 | int audio_clock_serial{-1};
481 | double audio_diff_cum{0}; /* used for AV difference average computation */
482 | double audio_diff_avg_coef{0};
483 | double audio_diff_threshold{0};
484 | int audio_diff_avg_count{0};
485 |
486 | int64_t audio_callback_time{0};
487 |
488 | #if defined(BUILD_WITH_AUDIO_FILTER) || defined(BUILD_WITH_VIDEO_FILTER)
489 | struct AudioParams audio_filter_src;
490 | #endif
491 | struct AudioParams audio_tgt;
492 |
493 | int video_stream{-1};
494 | AVStream *video_st{nullptr};
495 | PacketQueue videoPacketQueue_;
496 | FrameQueue pictureQueue_;
497 | Decoder videoDecoder_;
498 |
499 | int subtitle_stream{-1};
500 | AVStream *subtitle_st{nullptr};
501 | PacketQueue subtitlePacketQueue_;
502 | FrameQueue subtitleQueue_;
503 | Decoder subtitleDecoder_;
504 |
505 | int data_stream{-1};
506 | AVStream *data_st{nullptr};
507 | PacketQueue dataPacketQueue_;
508 | std::thread data_tid_;
509 |
510 | double frame_last_returned_time{0};
511 | double frame_last_filter_delay{0};
512 |
513 | int paused{0};
514 | int last_paused{0};
515 | int queue_attachments_req{0};
516 |
517 | SDL_AudioDeviceID audio_dev{0};
518 |
519 | #if defined(BUILD_WITH_AUDIO_FILTER) || defined(BUILD_WITH_VIDEO_FILTER)
520 | int vfilter_idx{0};
521 | AVFilterContext *in_video_filter{nullptr}; // the first filter in the video chain
522 | AVFilterContext *out_video_filter{nullptr}; // the last filter in the video chain
523 | AVFilterContext *in_audio_filter{nullptr}; // the first filter in the audio chain
524 | AVFilterContext *out_audio_filter{nullptr}; // the last filter in the audio chain
525 | AVFilterGraph *agraph{nullptr}; // audio filter graph
526 | #endif
527 |
528 | int64_t duration_{AV_NOPTS_VALUE};
529 | int64_t start_time_{0};
530 |
531 | int videoSerial_{0};
532 | int audioSerial_{0};
533 | int dataSerial_{0};
534 | int subtitleSerial_{0};
535 |
536 | bool abort_reading_{false};
537 | bool eof_{false};
538 | std::thread read_tid_;
539 | std::condition_variable continue_read_thread_;
540 | std::mutex wait_mtx;
541 |
542 | EventQueue evq_;
543 |
544 | // video
545 | int frame_drops_early{0};
546 | int frame_drops_late{0};
547 |
548 | bool drop_frame_mode{false};
549 |
550 | AVRational data_time_base_{1, AV_TIME_BASE};
551 | AVRational video_time_base_{1, AV_TIME_BASE};
552 | AVRational video_frame_rate_{0};
553 | double frame_duration_{0};
554 |
555 | bool force_refresh_{false};
556 |
557 | double frame_timer_{0};
558 |
559 | ConverterContext yuv_ctx_;
560 | ConverterContext sub_yuv_ctx_;
561 |
562 | public:
563 | OnStatus onStatus;
564 | OnMetaInfo onMetaInfo;
565 | OnStatics onStatics;
566 | OnClockUpdate onClockUpdate;
567 | OnIYUVDisplay onIYUVDisplay;
568 | OnAIData onAIData;
569 | OnLog onLog;
570 |
571 | public:
572 | AVDictionary *swr_opts{nullptr};
573 | AVDictionary *sws_dict{nullptr};
574 | AVDictionary *format_opts{nullptr};
575 | AVDictionary *codec_opts{nullptr};
576 |
577 | bool audio_disable{false};
578 | bool subtitle_disable{false};
579 | bool data_disable{false};
580 |
581 | string wanted_stream_spec[AVMEDIA_TYPE_NB];
582 |
583 | int64_t start_time{AV_NOPTS_VALUE};
584 | int64_t duration{AV_NOPTS_VALUE};
585 | int seek_by_bytes{-1};
586 | float seek_interval{10};
587 | int audio_volume{100};
588 |
589 | AVInputFormat *iformat{nullptr};
590 | string filename;
591 |
592 | bool fast{false};
593 | bool genpts{false};
594 | int lowres{0};
595 |
596 | int decoder_reorder_pts{-1};
597 | int av_sync_type{AV_SYNC_AUDIO_MASTER};
598 | int framedrop{-1};
599 | int infinite_buffer{-1};
600 |
601 | #if defined(BUILD_WITH_AUDIO_FILTER) || defined(BUILD_WITH_VIDEO_FILTER)
602 | vector vfilters_list;
603 | string afilters;
604 | int filter_nbthreads{0};
605 | #endif
606 |
607 | string audio_codec_name;
608 | string subtitle_codec_name;
609 | string video_codec_name;
610 |
611 | bool showStatus{false};
612 | };
613 |
614 | //
615 | //
616 | struct Detection_t {
617 | double pts;
618 | };
619 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-ffplay",
3 | "version": "1.0.1",
4 | "author": "R.J.",
5 | "description": "An electron-vue project",
6 | "license": "MIT",
7 | "main": "dist/electron/main.js",
8 | "scripts": {
9 | "build:pack": "electron-builder",
10 | "build:dir": "electron-builder --dir",
11 | "build:dist": "node --max_old_space_size=32768 .electron-vue/build.js",
12 | "build": "node --max_old_space_size=32768 .electron-vue/build.js && electron-builder",
13 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
14 | "dev": "node .electron-vue/dev-runner.js",
15 | "e2e": "npm run pack && mocha test/e2e",
16 | "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src test",
17 | "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src test",
18 | "configure:ffplay": "node cmake-tool configure -d node-ffplay --CDTHIRD_PARTY_DIR=../.third-party",
19 | "build:ffplay": "node cmake-tool build -d node-ffplay",
20 | "test": "npm run unit && npm run e2e",
21 | "unit": "karma start test/unit/karma.conf.js"
22 | },
23 | "build": {
24 | "asar": true,
25 | "productName": "electron-ffplay",
26 | "appId": "com.steveista.app",
27 | "directories": {
28 | "output": "release"
29 | },
30 | "files": [
31 | "dist/electron/**/*"
32 | ],
33 | "extraFiles": [
34 | "licenses/**/*",
35 | "*.dll"
36 | ],
37 | "dmg": {
38 | "contents": [
39 | {
40 | "x": 410,
41 | "y": 150,
42 | "type": "link",
43 | "path": "/Applications"
44 | },
45 | {
46 | "x": 130,
47 | "y": 150,
48 | "type": "file"
49 | }
50 | ]
51 | },
52 | "mac": {
53 | "icon": "resources/icons/icon.icns"
54 | },
55 | "win": {
56 | "icon": "resources/icons/icon.ico",
57 | "sign": ".electron-vue/sign.js"
58 | },
59 | "linux": {
60 | "icon": "resources/icons"
61 | }
62 | },
63 | "devDependencies": {
64 | "@babel/core": "^7.5.5",
65 | "@babel/plugin-proposal-class-properties": "^7.5.5",
66 | "@babel/plugin-transform-runtime": "^7.5.5",
67 | "@babel/preset-env": "^7.7.7",
68 | "@babel/runtime": "^7.5.5",
69 | "@mdi/font": "^4.1.95",
70 | "ajv": "^6.5.0",
71 | "babel-eslint": "^10.0.2",
72 | "babel-loader": "^8.0.6",
73 | "babel-minify-webpack-plugin": "^0.3.1",
74 | "babel-plugin-istanbul": "^4.1.6",
75 | "babel-plugin-transform-runtime": "^6.23.0",
76 | "babili-webpack-plugin": "^0.1.2",
77 | "balloon-css": "^1.0.4",
78 | "big-integer": "^1.6.48",
79 | "bluebird": "^3.7.2",
80 | "brace": "^0.11.1",
81 | "chai": "^4.1.2",
82 | "chalk": "^2.4.1",
83 | "cross-env": "^5.1.6",
84 | "css-loader": "^0.28.11",
85 | "del": "^3.0.0",
86 | "devtron": "^1.4.0",
87 | "electron": "^8.2.2",
88 | "electron-builder": "^20.43.0",
89 | "electron-debug": "^3.0.0",
90 | "electron-devtools-installer": "^2.2.4",
91 | "electron-log": "^4.0.3",
92 | "eslint": "^4.19.1",
93 | "eslint-config-standard": "^11.0.0",
94 | "eslint-friendly-formatter": "^4.0.1",
95 | "eslint-loader": "^2.0.0",
96 | "eslint-plugin-html": "^4.0.3",
97 | "eslint-plugin-import": "^2.12.0",
98 | "eslint-plugin-node": "^6.0.1",
99 | "eslint-plugin-promise": "^3.8.0",
100 | "eslint-plugin-standard": "^3.1.0",
101 | "file-loader": "^1.1.11",
102 | "fs-extra": "^9.1.0",
103 | "html-webpack-plugin": "^3.2.0",
104 | "inject-loader": "^4.0.1",
105 | "karma": "^2.0.2",
106 | "karma-chai": "^0.1.0",
107 | "karma-coverage": "^1.1.2",
108 | "karma-electron": "^6.0.0",
109 | "karma-mocha": "^1.3.0",
110 | "karma-sourcemap-loader": "^0.3.7",
111 | "karma-spec-reporter": "^0.0.32",
112 | "karma-webpack": "^3.0.0",
113 | "lodash": "^4.17.20",
114 | "mini-css-extract-plugin": "0.4.0",
115 | "mocha": "^5.2.0",
116 | "moment": "^2.24.0",
117 | "multispinner": "^0.2.1",
118 | "node-loader": "^0.6.0",
119 | "node-sass": "^4.9.2",
120 | "npmlog": "^4.1.2",
121 | "raw-loader": "^4.0.0",
122 | "rc": "^1.2.8",
123 | "require-dir": "^1.0.0",
124 | "sass-loader": "^7.0.3",
125 | "selectn": "^1.1.2",
126 | "spectron": "^3.8.0",
127 | "splitargs": "0.0.7",
128 | "style-loader": "^0.21.0",
129 | "url-loader": "^1.0.1",
130 | "vue": "^2.6.10",
131 | "vue-devtools": "^5.1.3",
132 | "vue-html-loader": "^1.2.4",
133 | "vue-i18n": "^8.11.2",
134 | "vue-loader": "^15.2.4",
135 | "vue-style-loader": "^4.1.0",
136 | "vue-template-compiler": "^2.5.16",
137 | "vuetify": "^2.1.15",
138 | "vuex": "^3.1.1",
139 | "webpack": "^4.15.1",
140 | "webpack-cli": "^3.0.8",
141 | "webpack-dev-server": "^3.1.4",
142 | "webpack-hot-middleware": "^2.22.2",
143 | "webpack-merge": "^4.1.3",
144 | "which": "^2.0.2",
145 | "xml2js": "^0.4.22",
146 | "yml-loader": "^2.1.0"
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/resources/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevevista/electron-ffplay/b41cecdedbe047a35b3a4d09ef7b2bf09a21f53a/resources/icons/256x256.png
--------------------------------------------------------------------------------
/resources/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevevista/electron-ffplay/b41cecdedbe047a35b3a4d09ef7b2bf09a21f53a/resources/icons/icon.icns
--------------------------------------------------------------------------------
/resources/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevevista/electron-ffplay/b41cecdedbe047a35b3a4d09ef7b2bf09a21f53a/resources/icons/icon.ico
--------------------------------------------------------------------------------
/src/components/app-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
![]()
6 |
7 | {{ title }}
8 |
9 |
10 | mdi-window-minimize
11 | mdi-window-restore
12 | mdi-window-maximize
13 | mdi-window-close
14 |
15 |
16 |
17 |
18 |
19 |
79 |
80 |
85 |
86 |
117 |
--------------------------------------------------------------------------------
/src/components/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
39 |
40 |
75 |
76 |
82 |
--------------------------------------------------------------------------------
/src/components/dialogs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 | {{ title }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ $t('common.close') }}
29 | {{ okText }}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
136 |
--------------------------------------------------------------------------------
/src/components/xplayer/controller.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
12 |
15 |
16 |
17 |
24 |
27 |
28 |
33 |
39 |
45 |
57 |
58 | {{ secondToTime(video.currentTime) }} / {{ secondToTime(video.durationTime) }}
59 |
60 |
61 |
62 |
63 |
66 |
71 |
76 |
80 |
85 |
86 |
87 |
88 |
00:00
89 |
90 |
95 |
96 |
97 |
98 |
99 |
349 |
350 |
603 |
--------------------------------------------------------------------------------
/src/components/xplayer/detection-canvas.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export function drawDetectionBox (ctx, type, rect, class_name) {
3 | // ctx.lineWidth = 5;
4 | ctx.strokeStyle = type === 0 ? 'green' : (type === 1 ? 'red' : 'blue');
5 | ctx.strokeRect(rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]);
6 | ctx.fillStyle = 'red';
7 | if (class_name) {
8 | ctx.font = '16px Georgia';
9 | ctx.fillText(class_name, rect[0], rect[1]);
10 | }
11 | }
12 |
13 | export function drawLandmark (ctx, landmarks) {
14 | ctx.strokeStyle = "green";
15 | ctx.strokeWidth = 1;
16 | for (let i = 0; i < landmarks.length / 2; i++) {
17 | const x = landmarks[i * 2]
18 | const y = landmarks[i * 2 + 1]
19 | ctx.beginPath();
20 | ctx.arc(x, y, 1, 0, 2 * Math.PI, true);
21 | ctx.stroke();
22 | }
23 | }
24 |
25 | export function drawDetection (ctx, det) {
26 | drawDetectionBox(ctx, det.type, det.rect, det.labelString)
27 | if (det.landmarks && det.landmarks.length) {
28 | drawLandmark(ctx, det.landmarks);
29 | }
30 | }
31 |
32 | export function h264ProfileToCodec (profile) {
33 | switch (profile) {
34 | case 'Main':
35 | return 'avc1.4d0028';
36 | case 'Baseline':
37 | return 'avc1.42001e';
38 | case 'Constrained Baseline':
39 | return 'avc1.42001f';
40 | case 'Extended':
41 | return 'avc1.580028'
42 | case 'High':
43 | return 'avc1.640028'
44 | case 'High 10':
45 | case 'High 10 Intra':
46 | return 'avc1.6e0028'
47 | case 'High 4:2:2':
48 | case 'High 4:2:2 Intra':
49 | return 'avc1.7a0028'
50 | case 'High 4:4:4':
51 | case 'High 4:4:4 Predictive':
52 | case 'High 4:4:4 Intra':
53 | return 'avc1.f40028'
54 | default:
55 | return 'avc1.42001e';
56 | }
57 | }
58 |
59 | export class DetectionCanvas {
60 | constructor (canvas) {
61 | this.canvas = canvas
62 | this.ctx = canvas.getContext('2d');
63 | }
64 |
65 | drawBox (type, rect, class_name) {
66 | drawDetectionBox(this.ctx, type, rect, class_name)
67 | }
68 |
69 | drawLandmark (landmarks) {
70 | drawLandmark(this.ctx, landmarks);
71 | }
72 |
73 | drawDetection (det) {
74 | drawDetection(this.ctx, det);
75 | }
76 |
77 | clear () {
78 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/xplayer/info-panel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
[x]
4 |
5 | Url
6 | {{ url }}
7 |
8 |
9 | Size
10 | {{ sizeDisplay }}
11 |
12 |
13 | Resolution
14 | {{ width }} x {{ height }}
15 |
16 |
17 | fps
18 | {{ fps }}
19 |
20 |
21 | Duration
22 | {{ codecp.duration }}
23 |
24 |
25 | Video
26 | {{ codecp.video }}
27 |
28 |
29 | Audio
30 | {{ codecp.audio }}
31 |
32 |
33 |
34 |
35 |
36 |
37 | Age
38 | {{ parseInt(objects[i-1].age) }}
39 |
40 |
41 | {{ objects[i-1].male > 0.6 ? 'Male' : 'Female' }}
42 | {{ objects[i-1].male > 0.6 ? objects[i-1].male.toFixed(2) : (1 - objects[i-1].male).toFixed(2) }}
43 |
44 |
45 | {{ objects[i-1].emotion.emotion }}
46 | {{ objects[i-1].emotion.confidence.toFixed(2) }}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
133 |
134 |
175 |
--------------------------------------------------------------------------------
/src/components/xplayer/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
29 |
30 |
31 |
32 |
78 |
--------------------------------------------------------------------------------
/src/components/xplayer/play-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | mdi-arrow-right-circle
8 |
9 |
10 |
16 |
25 |
26 | {{ item.name }}
27 | {{ item.url }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
109 |
110 |
309 |
--------------------------------------------------------------------------------
/src/components/xplayer/settings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $t('video.playControl') }}
5 | {{ $t('video.snapshot') }}
6 | AI
7 |
8 |
9 |
10 |
11 |
12 | {{ $t('video.settings.auto_next') }}
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{ $t('video.settings.snapshot_format') }}
32 |
33 | png
34 | jpeg
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 连拍模式
43 |
44 | 时间间隔
45 | 帧号间隔
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 连拍间隔
54 |
55 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 帧号间隔
72 |
73 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | {{ $t('ai.switch') }}
95 |
96 | {{ $t('ai.pedestrian') }}
97 | {{ $t('ai.face') }}
98 | {{ $t('ai.landmark') }}
99 | {{ $t('ai.vehicle') }}
100 | {{ $t('ai.plate') }}
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
124 |
--------------------------------------------------------------------------------
/src/components/xplayer/test_decode.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable*/
2 | import {Decoder} from '../../ffplay'
3 | import {EventEmitter} from 'events';
4 | import fs from 'fs'
5 |
6 | let frameSize = [
7 | 11727,
8 | 4582,
9 | 1456,
10 | 664,
11 | 776,
12 | 3797,
13 | 1434,
14 | 703,
15 | 677,
16 | 4446,
17 | 1476,
18 | 702,
19 | 743,
20 | 5640,
21 | 1655,
22 | 1037,
23 | 830,
24 | 4764,
25 | 1016,
26 | 697,
27 | 485,
28 | 4279,
29 | 957,
30 | 451,
31 | 541,
32 | 3538,
33 | 707,
34 | 508,
35 | 298,
36 | 2636,
37 | 430,
38 | 421,
39 | 3896,
40 | 1081,
41 | 622,
42 | 571,
43 | 4058,
44 | 1022,
45 | 611,
46 | 477,
47 | 3526,
48 | 917,
49 | 499,
50 | 505,
51 | 3473,
52 | 1204,
53 | 561,
54 | 625,
55 | 4322,
56 | 1211,
57 | 819,
58 | 511,
59 | 4564,
60 | 1222,
61 | 593,
62 | 760,
63 | 3768,
64 | 1167,
65 | 670,
66 | 538,
67 | 3671,
68 | 1015,
69 | 589,
70 | 645,
71 | 4105,
72 | 1265,
73 | 653,
74 | 710,
75 | 3837,
76 | 1332,
77 | 628,
78 | 712,
79 | 3769,
80 | 1162,
81 | 760,
82 | 630,
83 | 3636,
84 | 969,
85 | 897,
86 | 4692,
87 | 1448,
88 | 848,
89 | 729,
90 | 4932,
91 | 1589,
92 | 800,
93 | 836,
94 | 3728,
95 | 1570,
96 | 696,
97 | 1156,
98 | 5333,
99 | 2190,
100 | 1128,
101 | 1240,
102 | 4944,
103 | 1119,
104 | 760,
105 | 530,
106 | 3421,
107 | 648,
108 | 427,
109 | 440,
110 | 3225,
111 | 683,
112 | 440,
113 | 324,
114 | 2844,
115 | 534,
116 | 322,
117 | 379,
118 | 2770,
119 | 667,
120 | 414,
121 | 329,
122 | 2568,
123 | 702,
124 | 363,
125 | 397,
126 | 3351,
127 | 929,
128 | 438,
129 | 456,
130 | 3965,
131 | 1184,
132 | 566,
133 | 740,
134 | 5103,
135 | 1449,
136 | 662,
137 | 622,
138 | 4300,
139 | 1167,
140 | 614,
141 | 590,
142 | 3681,
143 | 1348,
144 | 675,
145 | 851,
146 | 4469,
147 | 1874,
148 | 1072,
149 | 1003,
150 | 4192,
151 | 1828,
152 | 974,
153 | 869,
154 | 3521,
155 | 1534,
156 | 829,
157 | 720,
158 | 4906,
159 | 2117,
160 | 993,
161 | 1649,
162 | 6121,
163 | 2386,
164 | 1728,
165 | 1229,
166 | 3624,
167 | 1129,
168 | 603,
169 | 575,
170 | 2698,
171 | 764,
172 | 515,
173 | 461,
174 | 3283,
175 | 1213,
176 | 600,
177 | 936,
178 | 5888,
179 | 2247,
180 | 1412,
181 | 1203,
182 | 3826,
183 | 991,
184 | 3312,
185 | 1011,
186 | 2710,
187 | 2465,
188 | 2398,
189 | 2363,
190 | 2847,
191 | 2830,
192 | 2723,
193 | 4034,
194 | 1224,
195 | 2328,
196 | 2556,
197 | 2747,
198 | 2931,
199 | 3095,
200 | 2934,
201 | 3007,
202 | 6070,
203 | 2441,
204 | 1198,
205 | 1103,
206 | 1816,
207 | 2331,
208 | 2820,
209 | 2778,
210 | 2397,
211 | 2459,
212 | 2464,
213 | 2581,
214 | 2380,
215 | 2184,
216 | 2476,
217 | 3283,
218 | 4155,
219 | 4041,
220 | 4012,
221 | 3768,
222 | 15280,
223 | 841,
224 | 628,
225 | 11795,
226 | 1044,
227 | 550,
228 | 494,
229 | 5967,
230 | 827,
231 | 522,
232 | 404,
233 | 8338,
234 | 653,
235 | 371,
236 | 327,
237 | 9169,
238 | 964,
239 | 393,
240 | 440,
241 | 6349,
242 | 513,
243 | 313,
244 | 487,
245 | 7677,
246 | 1069,
247 | 505,
248 | 520,
249 | 7883,
250 | 901,
251 | 619,
252 | 544,
253 | 7344,
254 | 807,
255 | 502,
256 | 314,
257 | 33123,
258 | 5625,
259 | 467,
260 | 278,
261 | 165,
262 | 4590,
263 | 604,
264 | 419,
265 | 67,
266 | 5746,
267 | 266,
268 | 214,
269 | 451,
270 | 5018,
271 | 507,
272 | 208,
273 | 283,
274 | 5388,
275 | 845,
276 | 317,
277 | 139,
278 | 5015,
279 | 387,
280 | 132,
281 | 212,
282 | 5159,
283 | 458,
284 | 174,
285 | 158,
286 | 5264,
287 | 228,
288 | 160,
289 | 224,
290 | 4882,
291 | 615,
292 | 307,
293 | 368,
294 | 3745,
295 | 413,
296 | 380,
297 | 351,
298 | 2650,
299 | 617,
300 | 285,
301 | 168,
302 | 964,
303 | 811,
304 | 445,
305 | 324,
306 | 312
307 | ];
308 |
309 | const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
310 |
311 | async function loopPacket (fd, ff) {
312 | let decoer = new Decoder('h264')
313 |
314 | for (const size of frameSize) {
315 | const buffer = await readFile(fd, size)
316 |
317 | // decode (buffer[, size, offset, type, callback])
318 | // type = 0 padding YUV [width, height, ystride, ustride, vstride, Y, U, V]
319 | // type = 1 compat YUV [width, height, data]
320 | // type = 2 RGBA [width, height, data]
321 | let result = decoer.decode(buffer, size)
322 | // console.log('--------->', result)
323 | if (result) {
324 | let frame = {
325 | width: result[0],
326 | height: result[1],
327 | format: {
328 | cropLeft: 0,
329 | cropTop: 0,
330 | cropWidth: result[0],
331 | cropHeight: result[1]
332 | },
333 | y: { bytes: result[5], stride: result[2] },
334 | u: { bytes: result[6], stride: result[3] },
335 | v: { bytes: result[7], stride: result[4] }
336 | }
337 |
338 | ff.emit('yuv', frame)
339 | await delay(50);
340 | }
341 | }
342 |
343 | ff.emit('end')
344 | }
345 |
346 | function openFile (url) {
347 | return new Promise((resolve, reject) => {
348 | fs.open(url, 'r', (err, fd) => {
349 | if (err) reject(err)
350 | else resolve(fd)
351 | })
352 | })
353 | }
354 |
355 | function readFile (fd, size) {
356 | let buffer = Buffer.alloc(size)
357 | return new Promise((resolve, reject) => {
358 | fs.read(fd, buffer, 0, size, null, (err) => {
359 | if (err) reject(err)
360 | else resolve(buffer)
361 | });
362 | })
363 | }
364 |
365 | export function DecodeSync (vargs) {
366 | let url = vargs[vargs.length - 1] // 'C:\\Users\\rjzhou\\Documents\\foreman_352x288_30fps.h264'
367 | let emit = new EventEmitter()
368 |
369 | openFile(url).then(fd => loopPacket(fd, emit))
370 |
371 | emit.quit = () => {}
372 | emit.toogle_pause = () => {}
373 | emit.toogle_mute = () => {}
374 | emit.volume_up = () => {}
375 | emit.volume_down = () => {}
376 | emit.volume = (v) => {}
377 | emit.seek = (v) => {}
378 | emit.seek_to = (v) => {}
379 |
380 | return emit
381 | }
382 |
383 | export async function DecodeInPlayer (ff, url) {
384 | ff.open_video_decode('h264')
385 |
386 | const fd = await openFile(url)
387 |
388 | for (const size of frameSize) {
389 | const buffer = await readFile(fd, size)
390 | ff.send_video_data(buffer)
391 | await delay(50);
392 | }
393 | ff.quit()
394 | }
395 |
--------------------------------------------------------------------------------
/src/components/xplayer/video.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
495 |
496 |
521 |
--------------------------------------------------------------------------------
/src/components/xplayer/yuv-canvas.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2014-2016 Brion Vibber
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | the Software, and to permit persons to whom the Software is furnished to do so,
9 | subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | MPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 | /* eslint-disable*/
22 | "use strict";
23 |
24 | const shaders = {
25 | vertex: `
26 | precision lowp float;
27 |
28 | attribute vec2 aPosition;
29 | attribute vec2 aLumaPosition;
30 | attribute vec2 aChromaPosition;
31 | varying vec2 vLumaPosition;
32 | varying vec2 vChromaPosition;
33 |
34 | void main() {
35 | gl_Position = vec4(aPosition, 0, 1);
36 | vLumaPosition = aLumaPosition;
37 | vChromaPosition = aChromaPosition;
38 | }
39 | `,
40 | // inspired by https://github.com/mbebenita/Broadway/blob/master/Player/canvas.js
41 | fragment: `
42 | precision lowp float;
43 |
44 | uniform sampler2D uTextureY;
45 | uniform sampler2D uTextureCb;
46 | uniform sampler2D uTextureCr;
47 | varying vec2 vLumaPosition;
48 | varying vec2 vChromaPosition;
49 |
50 | void main() {
51 | // Y, Cb, and Cr planes are uploaded as LUMINANCE textures.
52 | float fY = texture2D(uTextureY, vLumaPosition).x;
53 | float fCb = texture2D(uTextureCb, vChromaPosition).x;
54 | float fCr = texture2D(uTextureCr, vChromaPosition).x;
55 |
56 | // Premultipy the Y...
57 | float fYmul = fY * 1.1643828125;
58 |
59 | // And convert that to RGB!
60 | gl_FragColor = vec4(fYmul + 1.59602734375 * fCr - 0.87078515625, fYmul - 0.39176171875 * fCb - 0.81296875 * fCr + 0.52959375, fYmul + 2.017234375 * fCb - 1.081390625, 1);
61 | }
62 | `,
63 | vertexStripe: `
64 | precision lowp float;
65 |
66 | attribute vec2 aPosition;
67 | attribute vec2 aTexturePosition;
68 | varying vec2 vTexturePosition;
69 |
70 | void main() {
71 | gl_Position = vec4(aPosition, 0, 1);
72 | vTexturePosition = aTexturePosition;
73 | }
74 | `,
75 | // extra 'stripe' texture fiddling to work around IE 11's poor performance on gl.LUMINANCE and gl.ALPHA textures
76 | // Y, Cb, and Cr planes are mapped into a pseudo-RGBA texture
77 | // so we can upload them without expanding the bytes on IE 11
78 | // which doesn't allow LUMINANCE or ALPHA textures
79 | // The stripe textures mark which channel to keep for each pixel.
80 | // Each texture extraction will contain the relevant value in one
81 | // channel only.
82 | fragmentStripe: `
83 | precision lowp float;
84 |
85 | uniform sampler2D uStripe;
86 | uniform sampler2D uTexture;
87 | varying vec2 vTexturePosition;
88 |
89 | void main() {
90 | float fLuminance = dot(texture2D(uStripe, vTexturePosition), texture2D(uTexture, vTexturePosition));
91 | gl_FragColor = vec4(fLuminance, fLuminance, fLuminance, 1);
92 | }
93 | `
94 | };
95 |
96 | /**
97 | * Warning: canvas must not have been used for 2d drawing prior!
98 | *
99 | * @param {HTMLCanvasElement} canvas - HTML canvas element to attach to
100 | * @constructor
101 | */
102 | function WebGLFrameSink(canvas, owner) {
103 | var self = this;
104 | const options = {
105 | // Don't trigger discrete GPU in multi-GPU systems
106 | preferLowPowerToHighPerformance: true,
107 | powerPreference: 'low-power',
108 | // Don't try to use software GL rendering!
109 | failIfMajorPerformanceCaveat: true,
110 | // In case we need to capture the resulting output.
111 | preserveDrawingBuffer: true
112 | };
113 |
114 | const gl = canvas.getContext('webgl2', options);
115 |
116 | if (gl === null) {
117 | throw new Error('WebGL unavailable');
118 | }
119 |
120 | function compileShader(type, source) {
121 | var shader = gl.createShader(type);
122 | gl.shaderSource(shader, source);
123 | gl.compileShader(shader);
124 |
125 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
126 | var err = gl.getShaderInfoLog(shader);
127 | gl.deleteShader(shader);
128 | throw new Error('GL shader compilation for ' + type + ' failed: ' + err);
129 | }
130 |
131 | return shader;
132 | }
133 |
134 |
135 | var program,
136 | unpackProgram,
137 | err;
138 |
139 | // In the world of GL there are no rectangles.
140 | // There are only triangles.
141 | // THERE IS NO SPOON.
142 | var rectangle = new Float32Array([
143 | // First triangle (top left, clockwise)
144 | -1.0, -1.0,
145 | +1.0, -1.0,
146 | -1.0, +1.0,
147 |
148 | // Second triangle (bottom right, clockwise)
149 | -1.0, +1.0,
150 | +1.0, -1.0,
151 | +1.0, +1.0
152 | ]);
153 |
154 | var textures = {};
155 | var framebuffers = {};
156 | var stripes = {};
157 | var buf, positionLocation, unpackPositionLocation;
158 | var unpackTexturePositionBuffer, unpackTexturePositionLocation;
159 | var stripeLocation, unpackTextureLocation;
160 | var lumaPositionBuffer, lumaPositionLocation;
161 | var chromaPositionBuffer, chromaPositionLocation;
162 |
163 | function createOrReuseTexture(name) {
164 | if (!textures[name]) {
165 | textures[name] = gl.createTexture();
166 | }
167 | return textures[name];
168 | }
169 |
170 | function uploadTexture(name, width, height, data) {
171 | var texture = createOrReuseTexture(name);
172 | gl.activeTexture(gl.TEXTURE0);
173 |
174 | if (WebGLFrameSink.stripe) {
175 | var uploadTemp = !textures[name + '_temp'];
176 | var tempTexture = createOrReuseTexture(name + '_temp');
177 | gl.bindTexture(gl.TEXTURE_2D, tempTexture);
178 | if (uploadTemp) {
179 | // new texture
180 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
181 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
182 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
183 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
184 | gl.texImage2D(
185 | gl.TEXTURE_2D,
186 | 0, // mip level
187 | gl.RGBA, // internal format
188 | width / 4,
189 | height,
190 | 0, // border
191 | gl.RGBA, // format
192 | gl.UNSIGNED_BYTE, // type
193 | data // data!
194 | );
195 | } else {
196 | // update texture
197 | gl.texSubImage2D(
198 | gl.TEXTURE_2D,
199 | 0, // mip level
200 | 0, // x offset
201 | 0, // y offset
202 | width / 4,
203 | height,
204 | gl.RGBA, // format
205 | gl.UNSIGNED_BYTE, // type
206 | data // data!
207 | );
208 | }
209 |
210 | var stripeTexture = textures[name + '_stripe'];
211 | var uploadStripe = !stripeTexture;
212 | if (uploadStripe) {
213 | stripeTexture = createOrReuseTexture(name + '_stripe');
214 | }
215 | gl.bindTexture(gl.TEXTURE_2D, stripeTexture);
216 | if (uploadStripe) {
217 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
218 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
219 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
220 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
221 | gl.texImage2D(
222 | gl.TEXTURE_2D,
223 | 0, // mip level
224 | gl.RGBA, // internal format
225 | width,
226 | 1,
227 | 0, // border
228 | gl.RGBA, // format
229 | gl.UNSIGNED_BYTE, //type
230 | buildStripe(width, 1) // data!
231 | );
232 | }
233 |
234 | } else {
235 | gl.bindTexture(gl.TEXTURE_2D, texture);
236 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
237 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
238 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
239 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
240 | gl.texImage2D(
241 | gl.TEXTURE_2D,
242 | 0, // mip level
243 | gl.LUMINANCE, // internal format
244 | width,
245 | height,
246 | 0, // border
247 | gl.LUMINANCE, // format
248 | gl.UNSIGNED_BYTE, //type
249 | data // data!
250 | );
251 | }
252 | }
253 |
254 | function unpackTexture(name, width, height) {
255 | var texture = textures[name];
256 |
257 | // Upload to a temporary RGBA texture, then unpack it.
258 | // This is faster than CPU-side swizzling in ANGLE on Windows.
259 | gl.useProgram(unpackProgram);
260 |
261 | var fb = framebuffers[name];
262 | if (!fb) {
263 | // Create a framebuffer and an empty target size
264 | gl.activeTexture(gl.TEXTURE0);
265 | gl.bindTexture(gl.TEXTURE_2D, texture);
266 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
267 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
268 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
269 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
270 | gl.texImage2D(
271 | gl.TEXTURE_2D,
272 | 0, // mip level
273 | gl.RGBA, // internal format
274 | width,
275 | height,
276 | 0, // border
277 | gl.RGBA, // format
278 | gl.UNSIGNED_BYTE, //type
279 | null // data!
280 | );
281 |
282 | fb = framebuffers[name] = gl.createFramebuffer();
283 | }
284 |
285 | gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
286 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
287 |
288 | var tempTexture = textures[name + '_temp'];
289 | gl.activeTexture(gl.TEXTURE1);
290 | gl.bindTexture(gl.TEXTURE_2D, tempTexture);
291 | gl.uniform1i(unpackTextureLocation, 1);
292 |
293 | var stripeTexture = textures[name + '_stripe'];
294 | gl.activeTexture(gl.TEXTURE2);
295 | gl.bindTexture(gl.TEXTURE_2D, stripeTexture);
296 | gl.uniform1i(stripeLocation, 2);
297 |
298 | // Rectangle geometry
299 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
300 | gl.enableVertexAttribArray(positionLocation);
301 | gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
302 |
303 | // Set up the texture geometry...
304 | gl.bindBuffer(gl.ARRAY_BUFFER, unpackTexturePositionBuffer);
305 | gl.enableVertexAttribArray(unpackTexturePositionLocation);
306 | gl.vertexAttribPointer(unpackTexturePositionLocation, 2, gl.FLOAT, false, 0, 0);
307 |
308 | // Draw into the target texture...
309 | gl.viewport(0, 0, width, height);
310 |
311 | gl.drawArrays(gl.TRIANGLES, 0, rectangle.length / 2);
312 |
313 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
314 |
315 | }
316 |
317 | function attachTexture(name, register, index) {
318 | gl.activeTexture(register);
319 | gl.bindTexture(gl.TEXTURE_2D, textures[name]);
320 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
321 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
322 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
323 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
324 |
325 | gl.uniform1i(gl.getUniformLocation(program, name), index);
326 | }
327 |
328 | function buildStripe(width) {
329 | if (stripes[width]) {
330 | return stripes[width];
331 | }
332 | var len = width,
333 | out = new Uint32Array(len);
334 | for (var i = 0; i < len; i += 4) {
335 | out[i ] = 0x000000ff;
336 | out[i + 1] = 0x0000ff00;
337 | out[i + 2] = 0x00ff0000;
338 | out[i + 3] = 0xff000000;
339 | }
340 | return stripes[width] = new Uint8Array(out.buffer);
341 | }
342 |
343 | function initProgram(vertexShaderSource, fragmentShaderSource) {
344 | var vertexShader = compileShader(gl.VERTEX_SHADER, vertexShaderSource);
345 | var fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource);
346 |
347 | var program = gl.createProgram();
348 | gl.attachShader(program, vertexShader);
349 | gl.attachShader(program, fragmentShader);
350 |
351 | gl.linkProgram(program);
352 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
353 | var err = gl.getProgramInfoLog(program);
354 | gl.deleteProgram(program);
355 | throw new Error('GL program linking failed: ' + err);
356 | }
357 |
358 | return program;
359 | }
360 |
361 | function init() {
362 | if (WebGLFrameSink.stripe) {
363 | unpackProgram = initProgram(shaders.vertexStripe, shaders.fragmentStripe);
364 | unpackPositionLocation = gl.getAttribLocation(unpackProgram, 'aPosition');
365 |
366 | unpackTexturePositionBuffer = gl.createBuffer();
367 | var textureRectangle = new Float32Array([
368 | 0, 0,
369 | 1, 0,
370 | 0, 1,
371 | 0, 1,
372 | 1, 0,
373 | 1, 1
374 | ]);
375 | gl.bindBuffer(gl.ARRAY_BUFFER, unpackTexturePositionBuffer);
376 | gl.bufferData(gl.ARRAY_BUFFER, textureRectangle, gl.STATIC_DRAW);
377 |
378 | unpackTexturePositionLocation = gl.getAttribLocation(unpackProgram, 'aTexturePosition');
379 | stripeLocation = gl.getUniformLocation(unpackProgram, 'uStripe');
380 | unpackTextureLocation = gl.getUniformLocation(unpackProgram, 'uTexture');
381 | }
382 | program = initProgram(shaders.vertex, shaders.fragment);
383 |
384 | buf = gl.createBuffer();
385 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
386 | gl.bufferData(gl.ARRAY_BUFFER, rectangle, gl.STATIC_DRAW);
387 |
388 | positionLocation = gl.getAttribLocation(program, 'aPosition');
389 | lumaPositionBuffer = gl.createBuffer();
390 | lumaPositionLocation = gl.getAttribLocation(program, 'aLumaPosition');
391 | chromaPositionBuffer = gl.createBuffer();
392 | chromaPositionLocation = gl.getAttribLocation(program, 'aChromaPosition');
393 | }
394 |
395 | /**
396 | * Actually draw a frame.
397 | * @param {YUVFrame} buffer - YUV frame buffer object
398 | */
399 | self.drawFrame = function(buffer) {
400 | const {width, height, cropLeft, cropTop, cropWidth, cropHeight} = buffer;
401 |
402 | var formatUpdate = (!program || canvas.width !== width || canvas.height !== height);
403 | if (formatUpdate) {
404 | // Keep the canvas at the right size...
405 | canvas.width = buffer.width;
406 | canvas.height = buffer.height;
407 | if (owner) {
408 | owner.changeWindowSize(width, height);
409 | }
410 | self.clear();
411 | }
412 |
413 | if (!program) {
414 | init();
415 | }
416 |
417 | if (formatUpdate) {
418 | // clear cache
419 | textures = {};
420 | framebuffers = {};
421 | stripes = {};
422 |
423 | var setupTexturePosition = function(buffer, location, texWidth) {
424 | // Warning: assumes that the stride for Cb and Cr is the same size in output pixels
425 | var textureX0 = cropLeft / texWidth;
426 | var textureX1 = (cropLeft + cropWidth) / texWidth;
427 | var textureY0 = (cropTop + cropHeight) / height;
428 | var textureY1 = cropTop / height;
429 | var textureRectangle = new Float32Array([
430 | textureX0, textureY0,
431 | textureX1, textureY0,
432 | textureX0, textureY1,
433 | textureX0, textureY1,
434 | textureX1, textureY0,
435 | textureX1, textureY1
436 | ]);
437 |
438 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
439 | gl.bufferData(gl.ARRAY_BUFFER, textureRectangle, gl.STATIC_DRAW);
440 | };
441 | setupTexturePosition(
442 | lumaPositionBuffer,
443 | lumaPositionLocation,
444 | buffer.y.stride);
445 | setupTexturePosition(
446 | chromaPositionBuffer,
447 | chromaPositionLocation,
448 | buffer.u.stride * 2);
449 | }
450 |
451 | // Create or update the textures...
452 | uploadTexture('uTextureY', buffer.y.stride, buffer.height, buffer.y.bytes);
453 | uploadTexture('uTextureCb', buffer.u.stride, buffer.height / 2, buffer.u.bytes);
454 | uploadTexture('uTextureCr', buffer.v.stride, buffer.height / 2, buffer.v.bytes);
455 |
456 | if (WebGLFrameSink.stripe) {
457 | // Unpack the textures after upload to avoid blocking on GPU
458 | unpackTexture('uTextureY', buffer.y.stride, buffer.height);
459 | unpackTexture('uTextureCb', buffer.u.stride, buffer.height / 2);
460 | unpackTexture('uTextureCr', buffer.v.stride, buffer.height / 2);
461 | }
462 |
463 | // Set up the rectangle and draw it
464 | gl.useProgram(program);
465 | gl.viewport(0, 0, canvas.width, canvas.height);
466 |
467 | attachTexture('uTextureY', gl.TEXTURE0, 0);
468 | attachTexture('uTextureCb', gl.TEXTURE1, 1);
469 | attachTexture('uTextureCr', gl.TEXTURE2, 2);
470 |
471 | // Set up geometry
472 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
473 | gl.enableVertexAttribArray(positionLocation);
474 | gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
475 |
476 | // Set up the texture geometry...
477 | gl.bindBuffer(gl.ARRAY_BUFFER, lumaPositionBuffer);
478 | gl.enableVertexAttribArray(lumaPositionLocation);
479 | gl.vertexAttribPointer(lumaPositionLocation, 2, gl.FLOAT, false, 0, 0);
480 |
481 | gl.bindBuffer(gl.ARRAY_BUFFER, chromaPositionBuffer);
482 | gl.enableVertexAttribArray(chromaPositionLocation);
483 | gl.vertexAttribPointer(chromaPositionLocation, 2, gl.FLOAT, false, 0, 0);
484 |
485 | // Aaaaand draw stuff.
486 | gl.drawArrays(gl.TRIANGLES, 0, rectangle.length / 2);
487 | };
488 |
489 | self.clear = function() {
490 | gl.viewport(0, 0, canvas.width, canvas.height);
491 | gl.clearColor(0.0, 0.0, 0.0, 0.0);
492 | gl.clear(gl.COLOR_BUFFER_BIT);
493 | };
494 |
495 | self.cropImage = function (cropCanvas, rect) {
496 | const width = rect[2] - rect[0];
497 | const height = rect[3] - rect[1]
498 | const y = canvas.height - rect[3]
499 |
500 | const ctx = cropCanvas.getContext('2d')
501 | const img = ctx.createImageData(width, height)
502 |
503 | gl.readPixels(rect[0], y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img.data);
504 | // console.log(img.data); // Uint8Array
505 | cropCanvas.width = width;
506 | cropCanvas.height = height;
507 |
508 | ctx.putImageData(img, 0, 0)
509 | }
510 |
511 | self.clear();
512 |
513 | return self;
514 | }
515 |
516 | // For Windows; luminance and alpha textures are ssllooww to upload,
517 | // so we pack into RGBA and unpack in the shaders.
518 | //
519 | // This seems to affect all browsers on Windows, probably due to fun
520 | // mismatches between GL and D3D.
521 | WebGLFrameSink.stripe = (function() {
522 | if (navigator.userAgent.indexOf('Windows') !== -1) {
523 | return true;
524 | }
525 | return false;
526 | })();
527 |
528 | module.exports = WebGLFrameSink;
529 |
530 |
--------------------------------------------------------------------------------
/src/ffplay.js:
--------------------------------------------------------------------------------
1 | import {requireAddon} from './import-dll'
2 |
3 | const binding = requireAddon('node-ffplay');
4 | const {EventEmitter} = require('events');
5 | const {inherits} = require('util');
6 | const {PlayBack, Decoder} = binding
7 |
8 | inherits(PlayBack, EventEmitter)
9 |
10 | PlayBack.prototype.command = function (...args) {
11 | this.send(...args)
12 | }
13 |
14 | PlayBack.prototype.quit = function (reason) {
15 | this.send('quit')
16 | }
17 |
18 | PlayBack.prototype.toogle_pause = function () {
19 | this.send('pause')
20 | }
21 |
22 | PlayBack.prototype.toogle_mute = function () {
23 | this.send('mute')
24 | }
25 |
26 | PlayBack.prototype.volume_up = function () {
27 | this.send('volume', 1)
28 | }
29 |
30 | PlayBack.prototype.volume_down = function () {
31 | this.send('volume', -1)
32 | }
33 |
34 | PlayBack.prototype.volume = function (v) {
35 | this.send('volume', 2, v)
36 | }
37 |
38 | PlayBack.prototype.seek = function (...args) {
39 | this.send('seek', ...args)
40 | }
41 |
42 | PlayBack.prototype.seek_to = function (v) {
43 | this.send('seek', 0, v)
44 | }
45 |
46 | PlayBack.prototype.speed = function (v) {
47 | this.send('speed', 0, v)
48 | }
49 |
50 | export {
51 | PlayBack,
52 | Decoder
53 | }
54 |
--------------------------------------------------------------------------------
/src/i18n/en.yml:
--------------------------------------------------------------------------------
1 | video:
2 | Loop: Loop
3 | Speed: Speed
4 | Normal: Normal
5 | load_fail: Load fail
6 | switching_to: Switching_to
7 | switched_to: Switched to
8 | quality: Quality
9 | FF: FF
10 | REW: REW
11 | Setting: Setting
12 | Full_screen: Full screen
13 | s: s
14 | Show_subtitle: Show subtitle
15 | Hide_subtitle: Hide subtitle
16 | Volume: Volume
17 | Live: Live
18 | Video_info: Video info
19 | prev_frame: Previous Frame
20 | next_frame: Next Frame
21 | detection: AI Detection
22 | in_screen: In Picture
23 | speed: Speed
24 | autoHideControl: Auto hide controller
25 | snapshot: Snapshot
26 | openUrl: Open URL
27 | openDir: Open Directory
28 | playControl: Play Control
29 | settings:
30 | snapshot_format: Snapshot saving format
31 | auto_next: Auto play next
32 | play_list:
33 | remove: Remove
34 | removeAll: Remove All
35 | add: Add
36 |
--------------------------------------------------------------------------------
/src/i18n/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 |
4 | Vue.use(VueI18n)
5 |
6 | const files = require.context('.', false, /\.yml$/)
7 | const messages = {}
8 |
9 | files.keys().forEach(key => {
10 | messages[key.replace(/(\.\/|\.yml)/g, '')] = files(key)
11 | })
12 |
13 | export const localeDisplay = {
14 | 'zh-cn': '中文',
15 | 'en': 'English'
16 | };
17 |
18 | export function detectLocale () {
19 | let locale = (navigator.language || navigator.browserLangugae).toLowerCase();
20 | switch (true) {
21 | case /^en.*/i.test(locale):
22 | locale = 'en'
23 | break
24 | case /^it.*/i.test(locale):
25 | locale = 'it'
26 | break
27 | case /^fr.*/i.test(locale):
28 | locale = 'fr'
29 | break
30 | case /^pt.*/i.test(locale):
31 | locale = 'pt'
32 | break
33 | case /^pt-BR.*/i.test(locale):
34 | locale = 'pt-br'
35 | break
36 | case /^ja.*/i.test(locale):
37 | locale = 'ja'
38 | break
39 | case /^zh-CN/i.test(locale):
40 | locale = 'zh-cn'
41 | break
42 | case /^zh-TW/i.test(locale):
43 | locale = 'zh-tw'
44 | break
45 | case /^zh.*/i.test(locale):
46 | locale = 'zh-cn'
47 | break
48 | case /^es.*/i.test(locale):
49 | locale = 'es'
50 | break
51 | case /^de.*/i.test(locale):
52 | locale = 'de'
53 | break
54 | case /^ru.*/i.test(locale):
55 | locale = 'ru'
56 | break
57 | case /^pl.*/i.test(locale):
58 | locale = 'pl'
59 | break
60 | default:
61 | locale = 'en'
62 | }
63 |
64 | return locale
65 | }
66 |
67 | const i18n = new VueI18n({
68 | locale: detectLocale(),
69 | messages
70 | });
71 |
72 | i18n.localeDisplay = localeDisplay;
73 |
74 | export default i18n;
75 |
--------------------------------------------------------------------------------
/src/i18n/zh-cn.yml:
--------------------------------------------------------------------------------
1 | video:
2 | Loop: 循环
3 | Speed: 速度
4 | Normal: 正常
5 | load_fail: 视频加载失败
6 | switching_to: 正在切换至
7 | switched_to: 已经切换至
8 | quality: 画质
9 | FF: 快进
10 | REW: 快退
11 | Setting: 设置
12 | Full_screen: 全屏
13 | s: 秒
14 | Show_subtitle: 显示字幕
15 | Hide_subtitle: 隐藏字幕
16 | Volume: 音量
17 | Live: 直播
18 | Video_info: 视频统计信息
19 | prev_frame: 上一帧
20 | next_frame: 下一帧
21 | detection: AI 侦测
22 | in_screen: 画中画
23 | speed: 播放速度
24 | autoHideControl: 自动隐藏控制栏
25 | snapshot: 截图
26 | openUrl: 打开流媒体
27 | openDir: 打开播放目录
28 | playControl: 播放控制
29 | settings:
30 | snapshot_format: 截图保存格式
31 | auto_next: 自动播放下一个
32 | play_list:
33 | remove: 移除
34 | removeAll: 清空
35 | add: 添加
36 |
--------------------------------------------------------------------------------
/src/import-dll.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | const PEPPER = 49
4 |
5 | const nodeMajor2Abis = {
6 | '8': 57,
7 | '9': 59,
8 | '10': 64,
9 | '11': 64,
10 | '12': 72,
11 | '13': 79,
12 | '14': 83,
13 | '15': 88
14 | }
15 |
16 | const ABI = nodeMajor2Abis[process.versions.node.split('.')[0]]
17 | const EXT = process.platform === 'win32' ? '.dll' : '.so'
18 | const archName = process.platform + '-' + process.arch
19 |
20 | /* eslint-disable no-undef */
21 |
22 | // build module directly to dll
23 | // rel: D:\dev\nivm-next\dist\win-unpacked\resources\app.asar\build
24 | // devl: D:\dev\nivm-next\build
25 | const rootPath = process.env.NODE_ENV === 'development' ? '..' : '../../..'
26 |
27 | __non_webpack_require__.extensions[EXT] = __non_webpack_require__.extensions['.node'];
28 |
29 | export function resolvePepperPath (name) {
30 | return path.resolve(__dirname, rootPath, `${name}-${archName}-pepper_${PEPPER}${EXT}`)
31 | }
32 |
33 | export function resolveAddonPath (name) {
34 | return path.resolve(__dirname, rootPath, `${name}-${archName}.abi-${ABI}${EXT}`)
35 | }
36 |
37 | export function requireAddon (modname) {
38 | return __non_webpack_require__(resolveAddonPath(modname))
39 | }
40 |
--------------------------------------------------------------------------------
/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NIVM
6 | <% if (htmlWebpackPlugin.options.nodeModules) { %>
7 |
8 |
11 | <% } %>
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import '@mdi/font/css/materialdesignicons.css'
2 | import 'vuetify/dist/vuetify.min.css'
3 | import 'balloon-css';
4 | import Vue from 'vue'
5 | import Vuetify from 'vuetify'
6 |
7 | import store from './store'
8 | import i18n from './i18n'
9 | import App from './components/app'
10 | import zhHans from 'vuetify/es5/locale/zh-Hans'
11 | import electron from 'electron'
12 |
13 | /* eslint-disable no-new */
14 | Vue.config.productionTip = false
15 | Vue.prototype.$electron = electron
16 |
17 | Vue.use(Vuetify)
18 |
19 | // load default settings
20 | const darkStr = localStorage.getItem('settings.theme.dark') || 'true';
21 | const themeDark = darkStr === 'true';
22 |
23 | const localeStr = localStorage.getItem('settings.locale');
24 | if (localeStr) {
25 | i18n.locale = localeStr;
26 | }
27 |
28 | const vm = new Vue({
29 | components: { App },
30 | store,
31 | i18n,
32 | vuetify: new Vuetify({
33 | lang: {
34 | locales: { 'zh-cn': zhHans },
35 | current: i18n.locale
36 | },
37 | theme: {
38 | dark: themeDark
39 | }
40 | }),
41 | template: ''
42 | }).$mount('#app')
43 |
44 | window.addEventListener('beforeunload', e => {
45 | // options are restored in app.mounted
46 | localStorage.setItem('settings.theme.dark', vm.$vuetify.theme.dark);
47 | localStorage.setItem('settings.locale', vm.$i18n.locale);
48 | store.commit('video/saveAllSettings')
49 | });
50 |
--------------------------------------------------------------------------------
/src/main/index.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is used specifically and only for development. It installs
3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to
4 | * modify this file, but it can be used to extend your development
5 | * environment.
6 | */
7 |
8 | /* eslint-disable */
9 |
10 | // Install `electron-debug` with `devtron`
11 | require('electron-debug')({ showDevTools: true, devToolsMode: 'detach' })
12 |
13 | // Install `vue-devtools`
14 | require('electron').app.on('ready', () => {
15 | require('electron').BrowserWindow.addDevToolsExtension('node_modules/vue-devtools/vender')
16 | /*
17 | let installExtension = require('electron-devtools-installer')
18 | installExtension.default(installExtension.VUEJS_DEVTOOLS)
19 | .then(() => {})
20 | .catch(err => {
21 | console.log('Unable to install `vue-devtools`: \n', err)
22 | })
23 | */
24 | })
25 |
26 | // Require `main` process to boot app
27 | require('./index')
28 |
--------------------------------------------------------------------------------
/src/main/index.js:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, globalShortcut } from 'electron'
2 |
3 | app.commandLine.appendSwitch('disable-site-isolation-trials');
4 | app.allowRendererProcessReuse = false;
5 |
6 | let mainWindow
7 | // let splashScreen
8 |
9 | const rootURL = process.env.NODE_ENV === 'development'
10 | ? `http://localhost:9080`
11 | : `file://${__dirname}`
12 |
13 | // app.commandLine.appendSwitch("ignore-gpu-blacklist");
14 | // app.commandLine.appendSwitch("register-pepper-plugins", 'ppapi_mpv.dll;application/x-mpvjs');
15 |
16 | function createWindow () {
17 | /**
18 | * Initial window options
19 | */
20 | mainWindow = new BrowserWindow({
21 | height: 698,
22 | useContentSize: true,
23 | width: 1200,
24 | show: false,
25 | webPreferences: {
26 | nodeIntegration: true,
27 | webviewTag: true,
28 | webSecurity: false,
29 | nativeWindowOpen: true
30 | // plugins: true
31 | },
32 | frame: false,
33 | autoHideMenuBar: true
34 | })
35 |
36 | mainWindow.once('ready-to-show', () => {
37 | // if (splashScreen) {
38 | // splashScreen.isDestroyed() || splashScreen.close();
39 | // splashScreen = null;
40 | // }
41 | mainWindow.show()
42 | })
43 |
44 | mainWindow.loadURL(rootURL + '/index.html')
45 |
46 | mainWindow.on('closed', () => {
47 | mainWindow = null
48 | })
49 | }
50 |
51 | app.on('ready', () => {
52 | globalShortcut.register('CommandOrControl+Shift+I', () => {
53 | mainWindow.webContents.toggleDevTools()
54 | })
55 |
56 | const gotTheLock = app.requestSingleInstanceLock()
57 |
58 | if (!gotTheLock) {
59 | app.quit()
60 | } else {
61 | app.on('second-instance', (event, commandLine, workingDirectory) => {
62 | // 当运行第二个实例时,将会聚焦到myWindow这个窗口
63 | if (mainWindow) {
64 | if (mainWindow.isMinimized()) mainWindow.restore()
65 | mainWindow.focus()
66 | }
67 | })
68 | }
69 |
70 | createWindow()
71 | /*
72 | splashScreen = new BrowserWindow({
73 | height: 600,
74 | width: 800,
75 | show: false,
76 | frame: false,
77 | autoHideMenuBar: true,
78 | center: true,
79 | webPreferences: {
80 | webSecurity: false
81 | }
82 | })
83 |
84 | splashScreen.once('ready-to-show', () => {
85 | splashScreen.show()
86 | })
87 |
88 | splashScreen.loadURL(process.env.NODE_ENV === 'development' ? `file://${__dirname}/../splash.html` : `file://${__dirname}/splash.html`)
89 | */
90 | })
91 |
92 | app.on('will-quit', function () {
93 | // Unregister all shortcuts.
94 | globalShortcut.unregisterAll()
95 | })
96 |
97 | app.on('window-all-closed', () => {
98 | if (process.platform !== 'darwin') {
99 | app.quit()
100 | }
101 | })
102 |
103 | app.on('activate', () => {
104 | if (mainWindow === null) {
105 | createWindow()
106 | }
107 | })
108 |
109 | /**
110 | * Auto Updater
111 | *
112 | * Uncomment the following code below and install `electron-updater` to
113 | * support auto updating. Code Signing with a valid certificate is required.
114 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
115 | */
116 |
117 | /*
118 | import { autoUpdater } from 'electron-updater'
119 |
120 | autoUpdater.on('update-downloaded', () => {
121 | autoUpdater.quitAndInstall()
122 | })
123 |
124 | app.on('ready', () => {
125 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates()
126 | })
127 | */
128 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | /**
5 | * The file enables `@/store/index.js` to import all vuex modules
6 | * in a one-shot manner. There should not be any reason to edit this file.
7 | */
8 |
9 | const files = require.context('.', false, /\.js$/)
10 | const modules = {}
11 |
12 | files.keys().forEach(key => {
13 | if (key === './index.js') return
14 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
15 | })
16 |
17 | Vue.use(Vuex)
18 |
19 | export default new Vuex.Store({
20 | modules,
21 | strict: process.env.NODE_ENV === 'development'
22 | })
23 |
--------------------------------------------------------------------------------
/src/store/video.js:
--------------------------------------------------------------------------------
1 | const streamingTransport = localStorage.getItem('settings.video.streamingTransport') || 'udp';
2 |
3 | let autoPlayNext = localStorage.getItem('settings.video.autoPlayNext');
4 | autoPlayNext = autoPlayNext === null || autoPlayNext === 'true'
5 |
6 | const snapshotFormat = localStorage.getItem('settings.video.snapshotFormat') || 'png';
7 |
8 | const state = {
9 | currentVideo: null,
10 | streamingTransport,
11 | autoPlayNext,
12 | snapshotFormat,
13 | usePcInference: false,
14 | inferenceFeatures: ['faces', 'age', 'emotions', 'vehicles']
15 | }
16 |
17 | const getters = {
18 | currentVideo (state) {
19 | return state.currentVideo
20 | }
21 | }
22 |
23 | const mutations = {
24 | setStreamingTransport (state, v) {
25 | state.streamingTransport = v
26 | },
27 | setAutoPlayNext (state, v) {
28 | state.autoPlayNext = v
29 | },
30 | setSnapshotFormat (state, v) {
31 | state.snapshotFormat = v
32 | },
33 | saveAllSettings (state) {
34 | localStorage.setItem('settings.video.streamingTransport', state.streamingTransport);
35 | localStorage.setItem('settings.video.autoPlayNext', state.autoPlayNext);
36 | },
37 | setPcInference (state, v) {
38 | state.usePcInference = v
39 | },
40 | setInferenceFeatures (state, v) {
41 | state.inferenceFeatures = v
42 | }
43 | }
44 |
45 | export default {
46 | namespaced: true,
47 | state,
48 | getters,
49 | mutations
50 | }
51 |
--------------------------------------------------------------------------------
/test/e2e/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // Set BABEL_ENV to use proper env config
4 | process.env.BABEL_ENV = 'test'
5 |
6 | // Enable use of ES6+ on required files
7 | require('babel-register')({
8 | ignore: /node_modules/
9 | })
10 |
11 | // Attach Chai APIs to global scope
12 | const { expect, should, assert } = require('chai')
13 | global.expect = expect
14 | global.should = should
15 | global.assert = assert
16 |
17 | // Require all JS files in `./specs` for Mocha to consume
18 | require('require-dir')('./specs')
19 |
--------------------------------------------------------------------------------
/test/e2e/specs/Launch.spec.js:
--------------------------------------------------------------------------------
1 | import utils from '../utils'
2 |
3 | describe('Launch', function () {
4 | beforeEach(utils.beforeEach)
5 | afterEach(utils.afterEach)
6 |
7 | it('shows the proper application title', function () {
8 | return this.app.client.getTitle()
9 | .then(title => {
10 | expect(title).to.equal('my-project')
11 | })
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/test/e2e/utils.js:
--------------------------------------------------------------------------------
1 | import electron from 'electron'
2 | import { Application } from 'spectron'
3 |
4 | export default {
5 | afterEach () {
6 | this.timeout(10000)
7 |
8 | if (this.app && this.app.isRunning()) {
9 | return this.app.stop()
10 | }
11 | },
12 | beforeEach () {
13 | this.timeout(10000)
14 | this.app = new Application({
15 | path: electron,
16 | args: ['src/main.js'],
17 | startTimeout: 10000,
18 | waitTimeout: 10000
19 | })
20 |
21 | return this.app.start()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | Vue.config.devtools = false
3 | Vue.config.productionTip = false
4 |
5 | // require all test files (files that ends with .spec.js)
6 | const testsContext = require.context('./specs', true, /\.spec$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all src files except main.js for coverage.
10 | // you can also change this to match only the subset of files that
11 | // you want coverage for.
12 | const srcContext = require.context('../../src/renderer', true, /^\.\/(?!main(\.js)?$)/)
13 | srcContext.keys().forEach(srcContext)
14 |
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const merge = require('webpack-merge')
5 | const webpack = require('webpack')
6 |
7 | const baseConfig = require('../../.electron-vue/webpack.renderer.config')
8 | const projectRoot = path.resolve(__dirname, '../../src/renderer')
9 |
10 | // Set BABEL_ENV to use proper preset config
11 | process.env.BABEL_ENV = 'test'
12 |
13 | let webpackConfig = merge(baseConfig, {
14 | devtool: '#inline-source-map',
15 | plugins: [
16 | new webpack.DefinePlugin({
17 | 'process.env.NODE_ENV': '"testing"'
18 | })
19 | ]
20 | })
21 |
22 | // don't treat dependencies as externals
23 | delete webpackConfig.entry
24 | delete webpackConfig.externals
25 | delete webpackConfig.output.libraryTarget
26 |
27 | // apply vue option to apply isparta-loader on js
28 | webpackConfig.module.rules
29 | .find(rule => rule.use.loader === 'vue-loader').use.options.loaders.js = 'babel-loader'
30 |
31 | module.exports = config => {
32 | config.set({
33 | browsers: ['visibleElectron'],
34 | client: {
35 | useIframe: false
36 | },
37 | coverageReporter: {
38 | dir: './coverage',
39 | reporters: [
40 | { type: 'lcov', subdir: '.' },
41 | { type: 'text-summary' }
42 | ]
43 | },
44 | customLaunchers: {
45 | 'visibleElectron': {
46 | base: 'Electron',
47 | flags: ['--show']
48 | }
49 | },
50 | frameworks: ['mocha', 'chai'],
51 | files: ['./index.js'],
52 | preprocessors: {
53 | './index.js': ['webpack', 'sourcemap']
54 | },
55 | reporters: ['spec', 'coverage'],
56 | singleRun: true,
57 | webpack: webpackConfig,
58 | webpackMiddleware: {
59 | noInfo: true
60 | }
61 | })
62 | }
63 |
--------------------------------------------------------------------------------
/test/unit/specs/LandingPage.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import LandingPage from '@/components/LandingPage'
3 |
4 | describe('LandingPage.vue', () => {
5 | it('should render correct contents', () => {
6 | const vm = new Vue({
7 | el: document.createElement('div'),
8 | render: h => h(LandingPage)
9 | }).$mount()
10 |
11 | expect(vm.$el.querySelector('.title').textContent).to.contain('Welcome to your new project!')
12 | })
13 | })
14 |
--------------------------------------------------------------------------------