├── static └── .gitkeep ├── tslint.json ├── src ├── web │ ├── components │ │ └── player │ │ │ ├── player.html │ │ │ ├── index.ts │ │ │ ├── player.less │ │ │ └── player.ts │ ├── styles │ │ ├── index.less │ │ └── variables.less │ ├── index.ts │ ├── application │ │ ├── index.ts │ │ ├── workspace.ts │ │ ├── context.ts │ │ └── workbench.ts │ ├── routes │ │ └── index.ts │ └── views │ │ ├── dashboard.less │ │ └── dashboard.vue ├── app │ ├── index.ts │ ├── context.ts │ └── workbench.ts └── index.ejs ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── appveyor.yml ├── LICENSE ├── README.md ├── tsconfig.json ├── .babelrc ├── .travis.yml └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-flagwind" 3 | } -------------------------------------------------------------------------------- /src/web/components/player/player.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{message}}
4 |
-------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/web/styles/index.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | @import "./variables.less"; -------------------------------------------------------------------------------- /src/web/components/player/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | export { default } from "./player"; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | 9 | # Editor directories and files 10 | .idea 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | *.mp3 -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = 4 | { 5 | "plugins": 6 | { 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "postcss-import": {}, 9 | "autoprefixer": {} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/web/styles/variables.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | @white-color: #fff; 9 | @primary-color: #000; 10 | @primary-border: #003f7a; 11 | @body-bg: @primary-color; 12 | @text-color: #727ec5; 13 | @text-highlight-color: #fff700; 14 | @text-size: 16px; -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import flagwind from "flagwind-core"; 9 | import ApplicationContext from "./context"; 10 | 11 | // 获取应用上下文 12 | let context = ApplicationContext.current; 13 | 14 | // 启动应用程序 15 | flagwind.Application.start(context); 16 | -------------------------------------------------------------------------------- /src/web/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import flagwind from "flagwind-core"; 9 | import ApplicationContext from "./application/context"; 10 | 11 | // 获取应用上下文 12 | let context = ApplicationContext.current; 13 | 14 | // 启动应用程序 15 | flagwind.Application.start(context); 16 | -------------------------------------------------------------------------------- /src/web/application/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import ApplicationContext from "./context"; 9 | import Workbench from "./workbench"; 10 | import Workspace from "./workspace"; 11 | 12 | export 13 | { 14 | ApplicationContext, 15 | Workbench, 16 | Workspace 17 | }; 18 | -------------------------------------------------------------------------------- /src/web/routes/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | const routes = 9 | [ 10 | { 11 | path: "/", 12 | redirect: "/dashboard" 13 | }, 14 | { 15 | name: "dashboard", 16 | path: "/dashboard", 17 | component: (resolve: any) => (require)(["src/web/views/dashboard.vue"], resolve) 18 | } 19 | ]; 20 | 21 | export default routes; 22 | -------------------------------------------------------------------------------- /src/web/views/dashboard.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | @import "~styles/variables.less"; 9 | 10 | .v-dashboard 11 | { 12 | position: absolute; 13 | top: 0; 14 | bottom: 0; 15 | left: 0; 16 | right: 0; 17 | font-size: @text-size; 18 | background-color: @body-bg; 19 | 20 | .ivu-row 21 | { 22 | margin-top: 20px; 23 | } 24 | 25 | .ivu-col 26 | { 27 | height: 400px; 28 | } 29 | } -------------------------------------------------------------------------------- /src/web/components/player/player.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | @import "~styles/variables.less"; 9 | 10 | .u-player 11 | { 12 | display: table; 13 | width: 100%; 14 | height: 100%; 15 | 16 | &-message 17 | { 18 | display:table-cell; 19 | width: 100%; 20 | height: 100%; 21 | vertical-align:middle; 22 | text-align: center; 23 | font-size: 18px; 24 | color: #fff; 25 | letter-spacing: 2px; 26 | background: #000; 27 | } 28 | 29 | &-video 30 | { 31 | width: 100%; 32 | height: 100%; 33 | } 34 | } -------------------------------------------------------------------------------- /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 | - choco install yarn --ignore-dependencies 25 | - git reset --hard HEAD 26 | - yarn 27 | - node --version 28 | 29 | build_script: 30 | #- yarn test 31 | - yarn build 32 | 33 | test: off 34 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RTSP Player 6 | <% if(htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 13 | 14 | 15 |
16 | 17 | <% if(!process.env.IS_RAW) { %> 18 | 24 | <% } %> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | # RTSP Player 2 | 3 | 一个基于 [`Electron`](https://github.com/electron/electron/tree/1-4-x/docs) + [`VLC`](https://github.com/jaruba/wcjs-player/wiki/JavaScript-API) 的 RTSP 视频播放解决方案,可用于播放 `海康`、`大华` 等支持 RTSP 协议的网络摄像头。 4 | 5 | ## 安装调试 6 | 7 | 安装依赖: 8 | 9 | ``` bash 10 | $ npm install 11 | ``` 12 | 13 | 调试: 14 | 15 | ``` bash 16 | $ npm run dev 17 | ``` 18 | 19 | ## 打包部署 20 | 21 | 打包配置在 `build\dist.config.js` 文件中修改,对应的配置项请浏览 22 | [electron-packager](https://github.com/electron-userland/electron-packager/blob/master/docs/api.md) 文档。 23 | 24 | 打包命令如下: 25 | 26 | ``` bash 27 | $ npm run dist 28 | ``` 29 | 30 | 打包后的文件在 `dist\app` 目录中。 31 | 32 | ## RTSP 协议 33 | 34 | 以海康威视IP摄像头为例: 35 | 36 | rtsp://[username]:[passwd]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream 37 | 38 | - username:用户名,例如admin 39 | - passwd:密码,例如12345 40 | - ip:设备的ip地址,例如192.0.0.64 41 | - port:端口号默认554,若为默认可以不写 42 | - codec:有h264、MPEG-4、mpeg4这几种 43 | - channel:通道号,起始为1 44 | - subtype:码流类型,主码流为main,子码流为sub 45 | 46 | 获取通道1的主码流,url如下: 47 | 48 | rtsp://admin:admin123@10.10.10.127:554/h264/ch1/main/av_stream 49 | rtsp://admin:admin123@10.10.10.127:554/mpeg4/ch1/main/av_stream -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": 3 | { 4 | "module": "es6", 5 | "target": "es6", 6 | "moduleResolution": "node", 7 | "allowJs": true, 8 | "sourceMap": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "allowSyntheticDefaultImports": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "src/*": ["./src/*"], 15 | "app/*": ["./src/app/*"], 16 | "web/*": ["./src/web/*"], 17 | "assets/*": ["./src/web/assets/*"], 18 | "styles/*": ["./src/web/styles/*"], 19 | "config/*": ["./src/web/config/*"], 20 | "components/*": ["./src/web/components/*"], 21 | "models/*": ["./src/web/models/*"], 22 | "services/*": ["./src/web/services/*"], 23 | "views/*": ["./src/web/views/*"], 24 | "utils/*": ["./src/web/utils/*"], 25 | "iview/*": ["./node_modules/flagwind-web/dist/typings/components/iview/typings/*"] 26 | }, 27 | "lib": 28 | [ 29 | "dom", 30 | "es6" 31 | ] 32 | } 33 | } -------------------------------------------------------------------------------- /src/web/views/dashboard.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 25 | 26 | 48 | -------------------------------------------------------------------------------- /src/web/application/workspace.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import Vue from "vue"; 9 | import flagwind from "flagwind-core"; 10 | import IWorkbench = flagwind.IWorkbench; 11 | import ApplicationContext from "./context"; 12 | 13 | /** 14 | * 提供工作空间的常用功能。 15 | * @class 16 | * @version 1.0.0 17 | */ 18 | export default class Workspace extends Vue 19 | { 20 | private _workbench: IWorkbench; 21 | 22 | /** 23 | * 获取工作空间所属的工作台实例。 24 | * @property 25 | * @returns IWorkbench 26 | */ 27 | public get workbench(): IWorkbench 28 | { 29 | return this._workbench; 30 | } 31 | 32 | /** 33 | * 初始化工作空间的新实例。 34 | * @constructor 35 | * @param {IWorkbench} workbench 工作台实例。 36 | * @param {Router} router 主路由程序。 37 | */ 38 | public constructor(workbench: IWorkbench) 39 | { 40 | let options = 41 | { 42 | el: "#workspace", 43 | router: ApplicationContext.current.router, 44 | store: ApplicationContext.current.store, 45 | template: '
' 46 | }; 47 | 48 | // 传入配置进行初始化 49 | super(options); 50 | 51 | // 保存工作台 52 | this._workbench = workbench; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "test": { 5 | "presets": [ 6 | [ 7 | "env", 8 | { 9 | "targets": { 10 | "node": 7 11 | } 12 | } 13 | ], 14 | "stage-0" 15 | ], 16 | "plugins": [ 17 | "istanbul" 18 | ] 19 | }, 20 | "app": { 21 | "presets": [ 22 | [ 23 | "env", 24 | { 25 | "targets": { 26 | "node": 7 27 | } 28 | } 29 | ], 30 | "stage-0" 31 | ] 32 | }, 33 | "web": { 34 | "presets": [ 35 | [ 36 | "env", 37 | { 38 | "modules": false 39 | } 40 | ], 41 | "stage-0" 42 | ] 43 | }, 44 | "raw": { 45 | "presets": [ 46 | [ 47 | "env", 48 | { 49 | "modules": false 50 | } 51 | ], 52 | "stage-0" 53 | ] 54 | } 55 | }, 56 | "plugins": [ 57 | "transform-runtime" 58 | ] 59 | } -------------------------------------------------------------------------------- /.travis.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 | osx_image: xcode8.3 4 | sudo: required 5 | dist: trusty 6 | language: c 7 | matrix: 8 | include: 9 | - os: osx 10 | - os: linux 11 | env: CC=clang CXX=clang++ npm_config_clang=1 12 | compiler: clang 13 | cache: 14 | directories: 15 | - node_modules 16 | - "$HOME/.electron" 17 | - "$HOME/.cache" 18 | addons: 19 | apt: 20 | packages: 21 | - libgnome-keyring-dev 22 | - icnsutils 23 | #- xvfb 24 | before_install: 25 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ 26 | "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz 27 | | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull 28 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi 29 | install: 30 | #- export DISPLAY=':99.0' 31 | #- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 32 | - nvm install 7 33 | - curl -o- -L https://yarnpkg.com/install.sh | bash 34 | - source ~/.bashrc 35 | - npm install -g xvfb-maybe 36 | - yarn 37 | script: 38 | #- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js 39 | #- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e 40 | - yarn run build 41 | branches: 42 | only: 43 | - master 44 | -------------------------------------------------------------------------------- /src/app/context.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import path from "path"; 9 | import flagwind from "flagwind-core"; 10 | import IWorkbench = flagwind.IWorkbench; 11 | import ApplicationContextBase = flagwind.ApplicationContextBase; 12 | import Workbench from "./workbench"; 13 | 14 | /** 15 | * 包含当前应用程序的上下文实例。 16 | * @class 17 | * @version 1.0.0 18 | */ 19 | export default class ApplicationContext extends ApplicationContextBase 20 | { 21 | private _mainUrl: string; 22 | 23 | /** 24 | * 获取当前应用的入口地址。 25 | * @property 26 | * @returns string 27 | */ 28 | public get mainUrl(): string 29 | { 30 | return this._mainUrl; 31 | } 32 | 33 | /** 34 | * 获取当前应用程序的上下文实例。 35 | * @static 36 | * @member 37 | */ 38 | public static readonly current: ApplicationContext = new ApplicationContext(); 39 | 40 | /** 41 | * 私有构造函数。 42 | * @private 43 | */ 44 | protected constructor() 45 | { 46 | super("wayto-toilets-client"); 47 | 48 | // 设置主窗口地址 49 | this._mainUrl = process.env.NODE_ENV === "development" ? "http://localhost:9080" : `file://${__dirname}/index.html`; 50 | 51 | /** 52 | * Set `__static` path to static files in production 53 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 54 | */ 55 | if(process.env.NODE_ENV !== "development") 56 | { 57 | global["__static"] = path.join(__dirname, "/static").replace(/\\/g, "\\\\"); 58 | } 59 | } 60 | 61 | /** 62 | * 创建一个工作台对象。 63 | * @override 64 | * @returns IWorkbench 65 | */ 66 | protected createWorkbench(args: Array): IWorkbench 67 | { 68 | return new Workbench(this); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/web/application/context.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import Router from "vue-router"; 9 | import Vuex, { Store } from "vuex"; 10 | import flagwind from "flagwind-core"; 11 | import IWorkbench = flagwind.IWorkbench; 12 | import ApplicationContextBase = flagwind.ApplicationContextBase; 13 | import InvalidOperationException = flagwind.InvalidOperationException; 14 | import Workbench from "./workbench"; 15 | 16 | /** 17 | * 包含当前应用程序的上下文实例。 18 | * @class 19 | * @version 1.0.0 20 | */ 21 | export default class ApplicationContext extends ApplicationContextBase 22 | { 23 | private _router: Router; 24 | private _store: Store; 25 | 26 | /** 27 | * 获取或设置当前应用的主路由对象。 28 | * @property 29 | * @returns Router 30 | */ 31 | public get router(): Router 32 | { 33 | return this._router; 34 | } 35 | 36 | public set router(value: Router) 37 | { 38 | if(!value) 39 | { 40 | throw new InvalidOperationException(); 41 | } 42 | 43 | this._router = value; 44 | } 45 | 46 | /** 47 | * 获取或设置当前应用的状态管理对象。 48 | * @property 49 | * @returns Store 50 | */ 51 | public get store(): Store 52 | { 53 | return this._store; 54 | } 55 | 56 | public set store(value: Store) 57 | { 58 | if(!value) 59 | { 60 | throw new InvalidOperationException(); 61 | } 62 | 63 | this._store = value; 64 | } 65 | 66 | /** 67 | * 获取当前应用程序的上下文实例。 68 | * @static 69 | * @member 70 | */ 71 | public static readonly current: ApplicationContext = new ApplicationContext(); 72 | 73 | /** 74 | * 私有构造函数。 75 | * @private 76 | */ 77 | protected constructor() 78 | { 79 | super("flagwind-rtsp-player"); 80 | } 81 | 82 | /** 83 | * 创建一个工作台对象。 84 | * @override 85 | * @returns IWorkbench 86 | */ 87 | protected createWorkbench(args: Array): IWorkbench 88 | { 89 | return new Workbench(this); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flagwind-rtsp-player", 3 | "version": "1.0.0", 4 | "author": "jason ", 5 | "description": "A RTSP Player.", 6 | "private": true, 7 | "main": "./dist/web/app.js", 8 | "scripts": { 9 | "dev": "node build/dev.js", 10 | "dist": "node build/dist.js" 11 | }, 12 | "dependencies": { 13 | "flagwind-core": "^1.1.0", 14 | "flagwind-web": "^1.0.7", 15 | "vue": "^2.5.7", 16 | "vue-router": "^3.0.1", 17 | "vuex": "^3.0.1", 18 | "wcjs-player": "^6.0.0", 19 | "wcjs-prebuilt": "^3.0.0" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^8.5.2", 23 | "babel-core": "^6.25.0", 24 | "babel-loader": "^7.1.1", 25 | "babel-plugin-istanbul": "^4.1.1", 26 | "babel-plugin-transform-runtime": "^6.23.0", 27 | "babel-preset-env": "^1.6.0", 28 | "babel-preset-stage-0": "^6.24.1", 29 | "babel-register": "^6.24.1", 30 | "babili-webpack-plugin": "^0.1.2", 31 | "cfonts": "^1.1.3", 32 | "chalk": "^2.1.0", 33 | "copy-webpack-plugin": "^4.0.1", 34 | "cross-env": "^5.0.5", 35 | "css-loader": "^0.28.4", 36 | "del": "^3.0.0", 37 | "devtron": "^1.4.0", 38 | "electron-packager": "8.7.2", 39 | "electron": "1.4.13", 40 | "extract-text-webpack-plugin": "^3.0.0", 41 | "file-loader": "^0.11.2", 42 | "html-webpack-plugin": "^2.30.1", 43 | "inject-loader": "^3.0.0", 44 | "less": "^2.7.3", 45 | "less-loader": "^4.0.5", 46 | "multispinner": "^0.2.1", 47 | "node-loader": "^0.6.0", 48 | "postcss-import": "^11.0.0", 49 | "postcss-loader": "^2.0.9", 50 | "require-dir": "^0.3.0", 51 | "style-loader": "^0.18.2", 52 | "ts-loader": "^3.2.0", 53 | "tslint": "^5.8.0", 54 | "tslint-config-flagwind": "^1.0.1", 55 | "tslint-loader": "^3.5.3", 56 | "typescript": "^2.6.2", 57 | "url-loader": "^0.5.9", 58 | "vue-html-loader": "^1.2.4", 59 | "vue-loader": "^13.6.0", 60 | "vue-style-loader": "^3.0.3", 61 | "vue-template-compiler": "^2.5.13", 62 | "webpack": "^3.10.0", 63 | "webpack-dev-server": "^2.9.7", 64 | "webpack-hot-middleware": "^2.21.0", 65 | "webpack-merge": "^4.1.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/app/workbench.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import path from "path"; 9 | import flagwind from "flagwind-core"; 10 | import WorkbenchBase = flagwind.WorkbenchBase; 11 | import ApplicationContextBase = flagwind.ApplicationContextBase; 12 | import ApplicationContext from "./context"; 13 | 14 | // tslint:disable-next-line:no-var-requires 15 | const electron = require("electron"); 16 | const app = electron.app; 17 | const BrowserWindow = electron.BrowserWindow; 18 | const globalShortcut = electron.globalShortcut; 19 | 20 | // Electron 1.4.x 文档 21 | // https://github.com/electron/electron/tree/1-4-x/docs 22 | 23 | export default class Workbench extends WorkbenchBase 24 | { 25 | private _mainWindow: any; 26 | 27 | /** 28 | * 获取当前应用的主窗口。 29 | * @returns BrowserWindow 30 | */ 31 | public get mainWindow(): any 32 | { 33 | return this._mainWindow; 34 | } 35 | 36 | /** 37 | * 初始化工作台的新实例。 38 | * @param {ApplicationContextBase} applicationContext 39 | */ 40 | public constructor(context: ApplicationContextBase) 41 | { 42 | super(context); 43 | } 44 | 45 | /** 46 | * 当工作台打开时调用。 47 | * @async 48 | * @protected 49 | * @virtual 50 | * @param {Array} args 51 | * @returns void 52 | */ 53 | protected async onOpen(args: Array): Promise 54 | { 55 | // 设置 VLC 的路径 56 | process.env["VLC_PLUGIN_PATH"] = path.join(__dirname, "../../node_modules/wcjs-prebuilt/bin/plugins"); 57 | 58 | app.on("ready", async() => 59 | { 60 | this.createMainWindow(); 61 | 62 | // 注册全局快捷键 63 | globalShortcut.register("f6", () => 64 | { 65 | this._mainWindow.openDevTools({detach: true}); 66 | }); 67 | 68 | // 注册全局快捷键 69 | globalShortcut.register("f11", () => 70 | { 71 | this._mainWindow.setFullScreen(!this._mainWindow.isFullScreen()); 72 | }); 73 | }); 74 | 75 | app.on("window-all-closed", () => 76 | { 77 | app.quit(); 78 | }); 79 | 80 | app.on("activate", () => 81 | { 82 | if(this._mainWindow === null) 83 | { 84 | this.createMainWindow(); 85 | } 86 | }); 87 | } 88 | 89 | /** 90 | * 创建主窗口。 91 | * @returns void 92 | */ 93 | private createMainWindow(): void 94 | { 95 | let context = this.applicationContext as ApplicationContext; 96 | 97 | // 测试版默认打开为最大化 98 | this._mainWindow = new BrowserWindow 99 | ({ 100 | width: 1460, 101 | height: 900, 102 | useContentSize: false 103 | }); 104 | 105 | if(process.env.NODE_ENV === "development") 106 | { 107 | // 隐藏菜单栏 108 | this._mainWindow.setMenuBarVisibility(false); 109 | } 110 | 111 | // 打开主地址 112 | this._mainWindow.loadURL(context.mainUrl); 113 | 114 | // 绑定关闭事件 115 | this._mainWindow.on("closed", () => 116 | { 117 | // 释放主窗口 118 | this._mainWindow = null; 119 | }); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/web/application/workbench.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import flagwind from "flagwind-core"; 9 | import WorkbenchBase = flagwind.WorkbenchBase; 10 | import ApplicationContextBase = flagwind.ApplicationContextBase; 11 | import ApplicationContext from "./context"; 12 | import Workspace from "./workspace"; 13 | 14 | import Vue from "vue"; 15 | import Router from "vue-router"; 16 | import routes from "../routes"; 17 | 18 | // 导入系统组件 19 | import { components } from "flagwind-web"; 20 | 21 | // 倒入全局样式 22 | import "flagwind-web/dist/styles/flagwind.css"; 23 | import "wcjs-player/css/general.css"; 24 | import "src/web/styles/index.less"; 25 | 26 | /** 27 | * 提供工作台的基本封装。 28 | * @class 29 | * @version 1.0.0 30 | */ 31 | export default class Workbench extends WorkbenchBase 32 | { 33 | private _workspace: Workspace; 34 | 35 | /** 36 | * 获取当前应用的主工作空间。 37 | * @property 38 | * @returns Workspace 39 | */ 40 | public get workspace(): Workspace 41 | { 42 | return this._workspace; 43 | } 44 | 45 | /** 46 | * 初始化工作台的新实例。 47 | * @param {ApplicationContextBase} applicationContext 48 | */ 49 | public constructor(context: ApplicationContextBase) 50 | { 51 | super(context); 52 | } 53 | 54 | /** 55 | * 当工作台打开时调用。 56 | * @async 57 | * @protected 58 | * @virtual 59 | * @param {Array} args 60 | * @returns void 61 | */ 62 | protected async onOpen(args: Array): Promise 63 | { 64 | let context = this.applicationContext as ApplicationContext; 65 | 66 | Vue.config.productionTip = false; 67 | 68 | Vue.config.errorHandler = (err, vm, info) => 69 | { 70 | console.error(err, vm, info); 71 | }; 72 | 73 | Vue.config.warnHandler = (msg, vm, trace) => 74 | { 75 | console.warn(msg, vm, trace); 76 | }; 77 | 78 | // 初始化组件 79 | this.initializeComponent(context); 80 | 81 | // 初始化路由程序 82 | this.initializeRouter(context); 83 | 84 | // 初始化状态管理程序 85 | this.initializeStore(context); 86 | 87 | // 初始化快捷键 88 | this.initializeShortcut(context); 89 | 90 | // 初始化工作空间 91 | this._workspace = this.createWorkspace(); 92 | } 93 | 94 | /** 95 | * 创建一个工作空间对象。 96 | * @override 97 | * @returns IWorkspace 98 | */ 99 | protected createWorkspace(): Workspace 100 | { 101 | return new Workspace(this); 102 | } 103 | 104 | /** 105 | * 初始化全局组件。 106 | * @param {ApplicationContext} context 应用程序上下文实例。 107 | * @returns void 108 | */ 109 | private initializeComponent(context: ApplicationContext): void 110 | { 111 | // 注册系统组件 112 | Vue.use(components); 113 | 114 | // 注册应用组件 115 | 116 | // 注册布局母版 117 | } 118 | 119 | /** 120 | * 初始化路由程序。 121 | * @param {ApplicationContext} context 应用程序上下文实例。 122 | * @returns void 123 | */ 124 | private initializeRouter(context: ApplicationContext): void 125 | { 126 | // 注册路由组件 127 | Vue.use(Router); 128 | 129 | // 初始化路由程序 130 | let router = new Router({routes}); 131 | 132 | // 设置路由程序 133 | context.router = router; 134 | } 135 | 136 | /** 137 | * 初始化状态管理程序。 138 | * @param {ApplicationContext} context 应用程序上下文实例。 139 | * @returns void 140 | */ 141 | private initializeStore(context: ApplicationContext): void 142 | { 143 | // 注册状态管理程序 144 | // Vue.use(Vuex); 145 | 146 | // 初始化状态容器 147 | // let store = new Vuex.Store 148 | // ({ 149 | // modules 150 | // }); 151 | 152 | // 设置状态容器 153 | // context.store = store; 154 | } 155 | 156 | /** 157 | * 初始化状态管理程序。 158 | * @param {ApplicationContext} context 应用程序上下文实例。 159 | * @returns void 160 | */ 161 | private initializeShortcut(context: ApplicationContext): void 162 | { 163 | window.addEventListener("keyup", (e: KeyboardEvent) => 164 | { 165 | // 绑定 F2 快捷键,跳转至设置视图 166 | if(e.keyCode === 113) 167 | { 168 | context.router.push("/settings"); 169 | } 170 | 171 | // 绑定 F8 快捷键,跳转至引导视图 172 | if(e.keyCode === 119) 173 | { 174 | context.router.push("/guide"); 175 | } 176 | 177 | }, true); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/web/components/player/player.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Authors: 3 | * jason 4 | * 5 | * Copyright (C) 2010-present Flagwind Inc. All rights reserved. 6 | */ 7 | 8 | import { component, config, watch, Component } from "flagwind-web"; 9 | import "./player.less"; 10 | 11 | // tslint:disable-next-line:variable-name no-var-requires 12 | const WebChimera = require("wcjs-player"); 13 | // tslint:disable-next-line:no-var-requires 14 | const wcjs = require("wcjs-prebuilt"); 15 | 16 | const seed = new Array("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "m", "n", "p", "Q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "2", "3", "4", "5", "6", "7", "8", "9"); 17 | const seedlength = seed.length; 18 | 19 | const getRandomCode = (size: number) => 20 | { 21 | let result = ""; 22 | 23 | for(let i = 0; i < size; i++) 24 | { 25 | const j = Math.floor(Math.random() * seedlength); 26 | 27 | result += seed[j]; 28 | } 29 | 30 | return result; 31 | }; 32 | 33 | /** 34 | * 播放器。 35 | * @class 36 | * @version 1.0.0 37 | */ 38 | @component 39 | ({ 40 | template: require("./player.html") 41 | }) 42 | export default class Player extends Component 43 | { 44 | private _videoId: string; // 播放器编号 45 | private _video: any; // 播放器实例 46 | 47 | /** 48 | * 获取视频编号。 49 | * @protected 50 | * @property 51 | * @returns string 52 | */ 53 | protected get videoId(): string 54 | { 55 | if(!this._videoId) 56 | { 57 | this._videoId = `player-${getRandomCode(6)}`; 58 | } 59 | 60 | return this._videoId; 61 | } 62 | 63 | public get video(): object 64 | { 65 | return this._video; 66 | } 67 | 68 | /** 69 | * 获取或设置需要显示的消息。 70 | * @public 71 | * @config {string} 72 | * @description 动态配置,支持响应式。 73 | */ 74 | @config({type: String}) 75 | public message: string; 76 | 77 | /** 78 | * 获取或设置播放地址。 79 | * @public 80 | * @config {string} 81 | * @description 动态配置,支持响应式。 82 | */ 83 | @config({required: true, type: String}) 84 | public src: string; 85 | 86 | /** 87 | * 获取或设置是否显示播放器UI。 88 | * @public 89 | * @config {boolean} 90 | * @default false 91 | * @description 动态配置,支持响应式。 92 | */ 93 | @config({type: Boolean, default: false}) 94 | public ui: boolean; 95 | 96 | /** 97 | * 获取或设置是否自动播放。 98 | * @public 99 | * @config {boolean} 100 | * @default true 101 | * @description 静态配置,不支持响应式。 102 | */ 103 | @config({type: Boolean, default: true}) 104 | public autoplay: boolean; 105 | 106 | /** 107 | * 获取或设置是否以静音方式播放。 108 | * @public 109 | * @config {boolean} 110 | * @default false 111 | * @description 静态配置,不支持响应式。 112 | */ 113 | @config({type: Boolean, default: false}) 114 | public mute: boolean; 115 | 116 | /** 117 | * 获取或设置是否自动循环播放。 118 | * @public 119 | * @config {boolean} 120 | * @default false 121 | * @description 静态配置,不支持响应式。 122 | */ 123 | @config({type: Boolean, default: false}) 124 | public loop: boolean; 125 | 126 | /** 127 | * 获取或设置是否允许全屏播放。 128 | * @public 129 | * @config {boolean} 130 | * @default true 131 | * @description 静态配置,不支持响应式。 132 | */ 133 | @config({type: Boolean, default: true}) 134 | public allowFullscreen: boolean; 135 | 136 | /** 137 | * 获取或设置网络资源缓存值(单位:毫秒)。 138 | * @public 139 | * @config {number} 140 | * @default 10000 141 | * @description 静态配置,不支持响应式。 142 | */ 143 | @config({type: Number, default: 10000}) 144 | public buffer: number; 145 | 146 | /** 147 | * 获取或设置标题栏何时可见。 148 | * @public 149 | * @config {string} 150 | * @default "none" 151 | * @description 取值范围:"fullscreen"、"minimized"、"both"、"none" 静态配置,不支持响应式。 152 | */ 153 | @config({type: String, default: "none"}) 154 | public titleBar: string; 155 | 156 | /** 157 | * 获取或设置VLC参数。 158 | * @public 159 | * @config {Array} 160 | * @description 静态配置,不支持响应式。 161 | * @see https://wiki.videolan.org/VLC_command-line_help 162 | */ 163 | @config({type: Array}) 164 | public vlcArgs: Array; 165 | 166 | /** 167 | * 获取或设置WCJS选项。 168 | * @public 169 | * @config {Object} 170 | * @description 静态配置,不支持响应式。 171 | * @see https://github.com/Magics-Group/wcjs-renderer 172 | */ 173 | @config({type: Object}) 174 | public wcjsRendererOptions: object; 175 | 176 | /** 177 | * 创建组件时调用的钩子方法。 178 | * @protected 179 | * @override 180 | * @returns void 181 | */ 182 | protected mounted(): void 183 | { 184 | try 185 | { 186 | const options: any = 187 | { 188 | wcjs: wcjs, 189 | autoplay: this.autoplay, 190 | mute: this.mute, 191 | loop: this.loop, 192 | allowFullscreen: this.allowFullscreen, 193 | buffer: this.buffer, 194 | titleBar: this.titleBar 195 | }; 196 | 197 | if(this.vlcArgs) 198 | { 199 | options.vlcArgs = this.vlcArgs; 200 | } 201 | 202 | if(this.wcjsRendererOptions) 203 | { 204 | options.wcjsRendererOptions = this.wcjsRendererOptions; 205 | } 206 | 207 | // 初始化播放器 208 | this._video = new WebChimera(`#${this.videoId}`).addPlayer(options); 209 | 210 | this._video.onError = (error: any) => 211 | { 212 | this.onPlayError(error); 213 | }; 214 | 215 | // 设置播放器UI 216 | this.setUserInterface(this.ui); 217 | 218 | // 设置视频源 219 | this.setSource(this.src); 220 | } 221 | catch(ex) 222 | { 223 | this.onPlayError(ex); 224 | } 225 | } 226 | 227 | /** 228 | * 当 "src" 发生变动时调用。 229 | * @protected 230 | * @param {String} src 视频源。 231 | * @returns void 232 | */ 233 | @watch("src") 234 | protected onSourceChange(src: string): void 235 | { 236 | this.setSource(src); 237 | } 238 | 239 | /** 240 | * 当 "ui" 发生变动时调用。 241 | * @protected 242 | * @param {boolean} ui 是否显示UI界面。 243 | * @returns void 244 | */ 245 | @watch("ui") 246 | protected onUIChange(ui: boolean): void 247 | { 248 | this.setUserInterface(ui); 249 | } 250 | 251 | /** 252 | * 设置播放器的视频源。 253 | * @private 254 | * @param {string} src 视频源。 255 | * @returns void 256 | */ 257 | private setSource(src: string): void 258 | { 259 | if(src) 260 | { 261 | this._video.pause(); 262 | this._video.clearPlaylist(); 263 | this._video.addPlaylist(src); 264 | this._video.play(); 265 | } 266 | } 267 | 268 | /** 269 | * 设置播放器的UI界面是否显示。 270 | * @param {boolean} show 271 | * @returns void 272 | */ 273 | private setUserInterface(show: boolean): void 274 | { 275 | this._video.ui(show); 276 | } 277 | 278 | private onPlayError(error: any): void 279 | { 280 | this.message = "播放失败"; 281 | 282 | console.error(error); 283 | } 284 | } 285 | --------------------------------------------------------------------------------