Compare commits

...

1383 Commits

Author SHA1 Message Date
renovate[bot]
062fc1dd06 chore(deps): update actions/checkout action to v6 2026-03-27 05:11:39 +00:00
renovate[bot]
0a2ea1e09d chore(deps): update dependency @babel/core to v7.29.0 (#2504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 01:29:47 +00:00
renovate[bot]
be77363515 chore(deps): update babel monorepo to v7.28.6 (#2501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 05:51:38 +00:00
renovate[bot]
b1a1c5c01b chore(deps): update babel monorepo to v7.28.5 (#2494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 03:47:34 +00:00
renovate[bot]
6b60360ce1 chore(deps): update babel monorepo to v7.28.4 (#2488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 16:07:58 +00:00
renovate[bot]
56e92101b0 chore(deps): update babel monorepo to v7.28.3 (#2487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 12:46:15 +00:00
renovate[bot]
2a5c67f9e3 fix(deps): update dependency @electron/remote to v2.1.3 (#2485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 21:13:11 +00:00
renovate[bot]
ef39f41e96 chore(deps): update babel monorepo to v7.28.0 (#2484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 13:11:16 +00:00
renovate[bot]
d317ea3ff8 chore(deps): update dependency @babel/core to v7.27.7 (#2482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 15:49:04 +00:00
renovate[bot]
787e4c441b chore(deps): update dependency @babel/eslint-parser to v7.27.5 (#2480)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 16:06:08 +00:00
renovate[bot]
ef116d01b7 chore(deps): update dependency @babel/core to v7.27.4 (#2479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-31 17:03:45 +00:00
renovate[bot]
0a5be1f166 chore(deps): update dependency @babel/core to v7.27.3 (#2478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 09:28:35 +00:00
renovate[bot]
4a35518da7 chore(deps): update dependency @babel/cli to v7.27.2 (#2475)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 20:22:11 +00:00
renovate[bot]
8f3301cd1e chore(deps): update babel monorepo to v7.27.1 (#2474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 17:29:51 +00:00
renovate[bot]
97b10558f6 chore(deps): update babel monorepo (#2466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-27 12:10:52 +00:00
renovate[bot]
e61501f13f chore(deps): update babel monorepo (#2459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 21:49:47 +00:00
renovate[bot]
b1d1778479 chore(deps): update dependency @babel/eslint-parser to v7.26.5 (#2457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 14:14:09 +00:00
renovate[bot]
9cca2d39c3 chore(deps): update babel monorepo (#2453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-08 21:05:11 +00:00
renovate[bot]
fcee4017d7 chore(deps): update dependency tape to v5.9.0 2024-10-16 06:34:29 +00:00
renovate[bot]
2890419d6d chore(deps): update babel monorepo 2024-10-16 05:21:16 +00:00
renovate[bot]
0fe3dd977f chore(deps): update dependency @babel/cli to v7.25.6 2024-08-30 13:58:05 +00:00
renovate[bot]
9ba088f76f chore(deps): update dependency electron-winstaller to v5.4.0 2024-08-12 11:46:38 +00:00
renovate[bot]
1a7404ee8a chore(deps): update babel monorepo 2024-08-12 11:31:09 +00:00
renovate[bot]
f1de7606a5 chore(deps): update npm to v10 (#2426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-12 13:28:56 +02:00
Borewit
d4b493cebe Fix CI shield in README (#2437)
Replace deprecated Travis shield with GitHub Action CI shield
2024-08-12 13:24:03 +02:00
renovate[bot]
1df3d025bd chore(deps): update babel monorepo 2024-07-21 17:27:22 +00:00
renovate[bot]
61b7681fd1 chore(deps): update dependency tape to v5.8.1 2024-06-17 18:50:56 +00:00
renovate[bot]
ac7f16e71c chore(deps): update dependency tape to v5.8.0 2024-06-16 04:07:26 +00:00
renovate[bot]
b363dca77f chore(deps): update dependency electron-winstaller to v5.3.1 2024-06-09 19:36:43 +00:00
renovate[bot]
16cabd19ff chore(deps): update dependency electron to v27.3.11 2024-06-09 15:22:50 +00:00
renovate[bot]
390b4ad7ec chore(deps): update babel monorepo to v7.24.7 2024-06-09 12:08:50 +00:00
renovate[bot]
b3dc3292b7 chore(deps): update dependency electron-winstaller to v5.3.0 2024-04-02 19:13:47 +00:00
renovate[bot]
244a242bdc chore(deps): update dependency electron to v27.3.8 2024-04-02 16:20:07 +00:00
renovate[bot]
241f3cb9a3 chore(deps): update babel monorepo 2024-04-02 16:17:23 +00:00
Herwig Hochleitner
aa4fb3d08e chore(deps): Upgrade to Electron v27 (#2388) 2024-04-02 18:14:50 +02:00
renovate[bot]
ebaf9cf848 fix(deps): update dependency music-metadata to v7.14.0 2024-02-26 01:21:30 +00:00
renovate[bot]
df05db5583 fix(deps): update dependency @electron/remote to v2.1.2 2024-02-25 22:28:16 +00:00
renovate[bot]
1ccfa404d2 chore(deps): update dependency tape to v5.7.5 2024-02-25 19:37:03 +00:00
renovate[bot]
bc19caf8ee chore(deps): update dependency electron-winstaller to v5.2.2 2024-02-25 15:50:03 +00:00
renovate[bot]
36c24a4940 chore(deps): update babel monorepo 2024-02-25 12:07:37 +00:00
renovate[bot]
1cefcff3da chore(deps): update dependency depcheck to v1.4.7 2023-12-20 00:09:20 +00:00
renovate[bot]
1eb61201d6 chore(deps): update dependency electron-installer-debian to v3.2.0 2023-09-07 03:37:53 +00:00
renovate[bot]
3e514f9cf6 fix(deps): update dependency @electron/remote to v2.0.11 2023-09-07 01:35:26 +00:00
renovate[bot]
b8f1d950f2 chore(deps): update dependency depcheck to v1.4.5 2023-09-06 22:04:13 +00:00
renovate[bot]
a85effc8ec chore(deps): update babel monorepo to v7.22.15 2023-09-06 22:01:22 +00:00
renovate[bot]
cf846979b0 chore(deps): update babel monorepo to v7.22.10 2023-08-08 20:16:46 +00:00
renovate[bot]
9f630c9bfb chore(deps): update dependency tape to v5.6.6 2023-07-19 23:16:44 +00:00
renovate[bot]
1d66b6d069 chore(deps): update dependency tape to v5.6.5 2023-07-13 22:50:50 +00:00
renovate[bot]
6b83ed34b9 chore(deps): update babel monorepo to v7.22.9 2023-07-13 18:39:30 +00:00
renovate[bot]
a0aeae7e91 chore(deps): update dependency plist to v3.1.0 2023-07-07 20:28:35 +00:00
renovate[bot]
99e3058676 chore(deps): update babel monorepo 2023-07-07 15:29:59 +00:00
renovate[bot]
20d1d8a224 chore(deps): update babel monorepo to v7.22.6 2023-07-05 12:31:07 +00:00
renovate[bot]
0b34ff1148 chore(deps): update dependency tape to v5.6.4 2023-07-03 07:26:03 +00:00
renovate[bot]
4120fcf4af chore(deps): update babel monorepo to v7.22.5 2023-06-14 12:29:59 +00:00
renovate[bot]
7ea277ccf6 fix(deps): update dependency @electron/remote to v2.0.10 2023-06-07 00:00:55 +00:00
renovate[bot]
77a981178b chore(deps): update dependency @babel/plugin-transform-react-jsx to v7.22.3 2023-05-28 14:03:53 +00:00
renovate[bot]
ea19545ec6 chore(deps): update dependency @babel/core to v7.22.1 2023-05-27 19:28:13 +00:00
renovate[bot]
6f6ccb029c chore(deps): update babel monorepo to v7.22.0 2023-05-27 15:44:47 +00:00
renovate[bot]
43b2d28fb5 chore(deps): update babel monorepo to v7.21.8 2023-05-03 21:19:42 +00:00
renovate[bot]
f20d24ac3c chore(deps): update babel monorepo to v7.21.5 2023-04-30 02:00:10 +00:00
renovate[bot]
b1332ee76b chore(deps): update dependency @babel/core to v7.21.4 2023-04-01 15:08:16 +00:00
renovate[bot]
34b8f3fb64 fix(deps): update dependency music-metadata to v7.13.4 2023-03-30 20:52:34 +00:00
renovate[bot]
b4f98e91ff chore(deps): update dependency nodemon to v2.0.22 2023-03-23 22:26:52 +00:00
renovate[bot]
ebb8d23ef3 chore(deps): update dependency gh-release to v7 (#2301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 14:23:49 +01:00
renovate[bot]
e5bf6745bf chore(deps): update dependency pngjs to v7 (#2326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 14:14:54 +01:00
renovate[bot]
686f63b35f fix(deps): update dependency rimraf to v4 (#2328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 14:09:44 +01:00
renovate[bot]
4ed5f3ff5f chore(deps): update dependency appdmg to v0.6.6 (#2318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2023-03-16 14:01:51 +01:00
dependabot[bot]
af8379012a build(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 (#2313)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 13:58:45 +01:00
renovate[bot]
1c53102e12 chore(deps): update dependency minimist to v1.2.8 (#2319)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 13:52:55 +01:00
renovate[bot]
1a074b65e6 chore(deps): update dependency open to v8.4.2 (#2321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 13:42:26 +01:00
renovate[bot]
2052adde7d chore(deps): update dependency electron-installer-redhat to v3.4.0 2023-03-16 12:37:17 +00:00
renovate[bot]
eed9eebd45 fix(deps): update dependency webtorrent to v1.9.7 2023-03-16 12:31:43 +00:00
renovate[bot]
e36eacd05a fix(deps): update dependency music-metadata to v7.13.3 2023-03-16 12:26:12 +00:00
renovate[bot]
9c7cffa1a3 chore(deps): update dependency tape to v5.6.3 2023-03-16 12:20:53 +00:00
renovate[bot]
94d2bd7807 chore(deps): update dependency nodemon to v2.0.21 2023-03-16 12:15:21 +00:00
renovate[bot]
5efbf7e5c9 chore(deps): update babel monorepo 2023-03-16 12:10:29 +00:00
renovate[bot]
fce078defe fix(deps): update dependency music-metadata to v7.13.1 2023-01-06 17:32:19 +00:00
renovate[bot]
10f355ba0f chore(deps): update dependency @babel/core to v7.20.12 2023-01-05 19:46:05 +00:00
renovate[bot]
9d8293b970 chore(deps): update babel monorepo to v7.20.7 2022-12-23 12:23:19 +00:00
renovate[bot]
73b8b54ae8 fix(deps): update dependency @electron/remote to v2.0.9 2022-12-07 02:25:28 +00:00
renovate[bot]
bc49dbc41d fix(deps): update dependency webtorrent to v1.9.6 2022-12-04 00:00:17 +00:00
renovate[bot]
69b1ff58d0 chore(deps): update dependency @babel/core to v7.20.5 2022-11-29 15:34:34 +00:00
renovate[bot]
cf48311fcb fix(deps): update dependency webtorrent to v1.9.4 2022-11-21 14:56:21 +00:00
renovate[bot]
c919af1fd9 fix(deps): update dependency webtorrent to v1.9.3 2022-11-19 00:57:08 +00:00
dependabot[bot]
cdc5ac6538 build(deps): bump loader-utils from 1.4.0 to 1.4.2 (#2288)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-11 15:04:46 +01:00
renovate[bot]
05881478b5 fix(deps): update dependency webtorrent to v1.9.2 2022-11-11 05:52:42 +00:00
renovate[bot]
fb279010d0 chore(deps): update dependency electron-winstaller to v5.1.0 (#2277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-11 01:10:49 +01:00
renovate[bot]
e54f61c480 fix(deps): update dependency create-torrent to v5.0.9 (#2287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-11 01:00:56 +01:00
renovate[bot]
ab9468f84f chore(deps): update npm to v9 (#2283)
* chore(deps): update npm to v9

* Update package.json

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Diego Rodríguez Baquero <diego@protocol.ai>
2022-11-10 15:15:19 -05:00
renovate[bot]
0f20c0acd1 chore(deps): update dependency @babel/core to v7.20.2 2022-11-06 00:06:21 +00:00
renovate[bot]
a4ef6cb562 fix(deps): update dependency webtorrent to v1.9.1 2022-10-28 16:53:40 +00:00
renovate[bot]
21c260dc13 chore(deps): update dependency electron-notarize to v1.2.2 2022-10-22 01:44:22 +00:00
renovate[bot]
0df520c5ba chore(deps): update dependency @babel/core to v7.19.6 2022-10-21 12:02:18 +00:00
renovate[bot]
707461a264 chore(deps): update dependency minimist to v1.2.7 (#2271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-15 16:35:45 +02:00
renovate[bot]
a22f94df80 fix(deps): update dependency semver to v7.3.8 (#2268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-09 12:05:24 +02:00
renovate[bot]
a60e817e0c fix(deps): update dependency webtorrent to v1.8.32 2022-10-09 07:34:24 +00:00
renovate[bot]
f1865dda4f fix(deps): update dependency webtorrent to v1.8.31 2022-10-09 02:35:05 +00:00
renovate[bot]
8cdc6b1307 chore(deps): update dependency spectron to v19 (#2133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-10-01 16:31:12 +02:00
dependabot[bot]
660facf8c2 build(deps): bump jsonpointer and is-my-json-valid (#2264)
Bumps [jsonpointer](https://github.com/janl/node-jsonpointer) and [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid). These dependencies needed to be updated together.

Updates `jsonpointer` from 4.1.0 to 5.0.1
- [Release notes](https://github.com/janl/node-jsonpointer/releases)
- [Commits](https://github.com/janl/node-jsonpointer/compare/v4.1.0...v5.0.1)

Updates `is-my-json-valid` from 2.20.5 to 2.20.6
- [Release notes](https://github.com/mafintosh/is-my-json-valid/releases)
- [Commits](https://github.com/mafintosh/is-my-json-valid/compare/v2.20.5...v2.20.6)

---
updated-dependencies:
- dependency-name: jsonpointer
  dependency-type: indirect
- dependency-name: is-my-json-valid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-10-01 12:08:04 +02:00
renovate[bot]
fee5b42813 chore(deps): update actions/stale action to v6 (#2259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-01 12:03:49 +02:00
renovate[bot]
ddb8500082 chore(deps): update babel monorepo to v7.19.3 2022-09-28 18:55:54 +00:00
renovate[bot]
cc6af9829e chore(deps): update dependency tape to v5.6.1 2022-09-20 23:51:15 +00:00
renovate[bot]
cfba6b503a fix(deps): update dependency music-metadata to v7.13.0 2022-09-19 12:15:21 +00:00
renovate[bot]
04c766bb53 chore(deps): update dependency nodemon to v2.0.20 2022-09-17 13:23:51 +00:00
renovate[bot]
2389a1aa35 chore(deps): update babel monorepo to v7.19.1 2022-09-15 17:58:20 +00:00
renovate[bot]
430b4c409a fix(deps): update dependency webtorrent to v1.8.30 2022-09-12 22:50:38 +00:00
renovate[bot]
810952f234 chore(deps): update babel monorepo to v7.19.0 2022-09-07 01:54:36 +00:00
renovate[bot]
ae4a8e40f8 fix(deps): update dependency webtorrent to v1.8.29 2022-09-03 10:27:11 +00:00
renovate[bot]
ea100dda36 fix(deps): update webtorrent 2022-09-03 01:22:01 +00:00
Diego Rodríguez Baquero
46cbce00ca Update stale.yml 2022-09-02 11:07:08 -05:00
dependabot[bot]
00ebbc7e17 build(deps): bump protobufjs from 6.10.2 to 6.11.3 (#2214)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.2 to 6.11.3.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/v6.11.3/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/compare/v6.10.2...v6.11.3)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-28 18:48:05 +02:00
renovate[bot]
8109ce5410 fix(deps): update dependency iso-639-1 to v2.1.15 (#2213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-28 00:24:16 +02:00
renovate[bot]
9200070269 chore(deps): update dependency electron-packager to v15.5.2 2022-08-24 03:23:12 +00:00
renovate[bot]
16ab96e4ac chore(deps): update dependency @babel/core to v7.18.13 2022-08-23 19:04:25 +00:00
renovate[bot]
d8632f6386 chore(deps): update dependency tape to v5.6.0 2022-08-17 22:07:38 +00:00
renovate[bot]
5558bad0de fix(deps): update dependency music-metadata to v7.12.6 2022-08-05 12:27:45 +00:00
renovate[bot]
0f5b7fd80e chore(deps): update babel monorepo to v7.18.10 2022-08-02 19:54:53 +00:00
renovate[bot]
6a288efb57 fix(deps): update dependency music-metadata to v7.12.5 2022-07-25 10:26:40 +00:00
renovate[bot]
f07a307b0d chore(deps): update babel monorepo to v7.18.9 2022-07-19 11:40:08 +00:00
renovate[bot]
cb1de722fe chore(deps): update dependency plist to v3.0.6 2022-07-13 22:41:20 +00:00
renovate[bot]
45036451c7 chore(deps): update dependency nodemon to v2.0.19 2022-07-06 17:13:24 +00:00
renovate[bot]
58a4f7a34a fix(deps): update dependency music-metadata to v7.12.4 2022-07-05 12:43:25 +00:00
renovate[bot]
9082c6f63e fix(deps): update dependency webtorrent to v1.8.26 2022-07-04 02:50:37 +00:00
renovate[bot]
9fb8a2ae09 fix(deps): update webtorrent 2022-07-04 02:42:36 +00:00
renovate[bot]
0e501b498e fix(deps): update dependency create-torrent to v5.0.3 2022-07-03 19:35:47 +00:00
renovate[bot]
85e0c5709e chore(deps): update babel monorepo to v7.18.6 2022-06-28 19:52:17 +00:00
renovate[bot]
cee88a7e24 chore(deps): update dependency nodemon to v2.0.18 2022-06-24 15:37:09 +00:00
renovate[bot]
f503a46447 fix(deps): update dependency webtorrent to v1.8.24 2022-06-24 05:42:10 +00:00
renovate[bot]
1cdd54e421 fix(deps): update dependency webtorrent to v1.8.23 2022-06-23 05:47:20 +00:00
Renovate Bot
73be2513ad chore(deps): update dependency @babel/core to v7.18.5 2022-06-14 12:06:06 +00:00
Renovate Bot
e9eb22a530 chore(deps): update babel monorepo to v7.18.2 2022-05-26 11:32:46 +00:00
Renovate Bot
d17f287ba2 chore(deps): update dependency electron to v15.5.7 2022-05-25 20:03:10 +00:00
Renovate Bot
06b904b837 fix(deps): update dependency webtorrent to v1.8.22 2022-05-24 05:20:44 +00:00
Renovate Bot
807c6aebdd chore(deps): update dependency @babel/core to v7.18.0 2022-05-20 20:27:48 +00:00
Renovate Bot
ab54a7cfb3 chore(deps): update babel monorepo to v7.17.12 2022-05-17 20:07:55 +00:00
Renovate Bot
77d4eed9fc fix(deps): update dependency webtorrent to v1.8.20 2022-05-14 04:56:53 +00:00
Diego Rodriguez Baquero
9c73638d1b fix: standard 2022-05-13 17:15:56 -05:00
Alex Lavallee
3dd7ebb34b feat(Player Page): Add support for mediaSession (#2170)
* feat(Player Page): Add support for mediaSession

* Update player-page.js
2022-05-11 18:44:43 -05:00
Subin Siby
c943f39f6b feat: add preference to customize global trackers (#1836)
* Add preference to customize global trackers. Requires restart to apply

* Use IPC to pass global trackers list, torrent pause and resume will now update trackers

* Make the default tracker list an array from array of arrays

* Use globalThis instead of just global

Co-authored-by: Diego Rodríguez Baquero <github@diegorbaquero.com>
2022-05-11 18:43:54 -05:00
CommanderRoot
401698e616 refactor: replace deprecated String.prototype.substr() (#2173)
.substr() is deprecated so we replace it with .slice() which works similarily but isn't deprecated

Signed-off-by: Tobias Speicher <rootcommander@gmail.com>
2022-05-11 18:41:56 -05:00
Christopher Toth
c85b3e4fd1 feat(a11y): Added roles and labels to the playback controls (#2167) 2022-05-11 18:40:43 -05:00
renovate[bot]
be15ee073d chore(deps): update dependency electron to v15.5.5 (#2204)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:39:01 -05:00
renovate[bot]
d838e6276d chore(deps): update babel monorepo to v7.17.10 (#2203)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:38:43 -05:00
renovate[bot]
35af086fad chore(deps): update dependency electron-packager to v15.5.1 (#2197)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:38:31 -05:00
renovate[bot]
cf9fbf2b0e fix(deps): update dependency semver to v7.3.7 (#2192)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:38:09 -05:00
renovate[bot]
ef2225c8a5 fix(deps): update dependency bitfield to v4.1.0 (#2201)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:37:22 -05:00
renovate[bot]
ffacb6085d chore(deps): update dependency nodemon to v2.0.16 (#2202)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:37:00 -05:00
renovate[bot]
20a84359cd chore(deps): update dependency standard to v17 (#2198)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:33:58 -05:00
renovate[bot]
a3d123537f fix(deps): update dependency webtorrent to v1.8.19 (#2200)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-11 18:21:13 -05:00
Renovate Bot
30fb222fb9 chore(deps): update dependency gh-release to v6.0.4 2022-04-16 00:08:23 +00:00
Renovate Bot
4c2a08dd52 chore(deps): update dependency tape to v5.5.3 2022-04-09 20:19:02 +00:00
Renovate Bot
406c568c1e chore(deps): update dependency electron to v15.5.2 2022-04-07 21:01:30 +00:00
Renovate Bot
a86424b7df chore(deps): update dependency @babel/core to v7.17.9 2022-04-07 17:13:51 +00:00
Renovate Bot
f6ffc16964 fix(deps): update dependency music-metadata to v7.12.3 2022-04-02 19:49:42 +00:00
Renovate Bot
05e8a0d284 chore(deps): update dependency electron to v15.5.1 2022-03-31 18:17:15 +00:00
Renovate Bot
1dc38afc85 fix(deps): update dependency webtorrent to v1.8.13 2022-03-31 04:44:02 +00:00
renovate[bot]
ca73b3c286 chore(deps): update actions/cache action to v3 (#2171)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-30 12:21:51 +03:00
renovate[bot]
bad30e8f1a chore(deps): update dependency minimist to v1.2.6 (#2172)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-30 12:21:22 +03:00
Renovate Bot
56036e89af chore(deps): update dependency electron to v15.5.0 2022-03-30 02:28:31 +00:00
Renovate Bot
bb7a9bb018 fix(deps): update dependency webtorrent to v1.8.12 2022-03-28 05:58:37 +00:00
Renovate Bot
f9ba0750d6 fix(deps): update dependency webtorrent to v1.8.11 2022-03-28 00:47:47 +00:00
Renovate Bot
d98299268f fix(deps): update dependency webtorrent to v1.8.9 2022-03-27 00:08:53 +00:00
Renovate Bot
601d01fe85 fix(deps): update dependency parse-torrent to v9.1.5 2022-03-26 19:32:10 +00:00
Renovate Bot
b24cc29523 chore(deps): update dependency electron-notarize to v1.2.1 2022-03-25 10:03:02 +00:00
Renovate Bot
dd06ac7cc5 fix(deps): update dependency webtorrent to v1.8.8 2022-03-25 04:31:32 +00:00
Renovate Bot
4097c5ee18 chore(deps): update dependency electron to v15.4.2 2022-03-24 22:26:07 +00:00
Renovate Bot
240128943c chore(deps): update dependency plist to v3.0.5 2022-03-24 17:50:55 +00:00
Renovate Bot
6d8a2a0b9e chore(deps): update dependency @babel/core to v7.17.8 2022-03-19 21:08:10 +00:00
Renovate Bot
6433da4901 fix(deps): update dependency @electron/remote to v2.0.8 2022-03-16 21:23:58 +00:00
renovate[bot]
96b27fa1cb chore(deps): update dependency gh-release to v6.0.3 (#2165)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-16 12:21:09 -05:00
Renovate Bot
9134620fe7 chore(deps): update dependency @babel/core to v7.17.7 2022-03-15 19:13:56 +00:00
Renovate Bot
1ff1c035b7 fix(deps): update webtorrent 2022-03-11 16:29:50 +00:00
Renovate Bot
58e165eb30 fix(deps): update dependency @electron/remote to v2.0.7 2022-03-11 16:22:58 +00:00
Renovate Bot
4fdafc91c1 chore(deps): update dependency electron to v15.4.1 2022-03-11 16:14:31 +00:00
Renovate Bot
7ef740ebe0 fix(deps): update dependency webtorrent to v1.8.5 2022-03-06 13:25:50 +00:00
renovate[bot]
138ece60fd chore(deps): update actions/upload-artifact action to v3 (#2158)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-04 17:49:01 +01:00
renovate[bot]
a2d0cc09eb chore(deps): update actions/stale action to v5 (#2155)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 15:22:31 +01:00
renovate[bot]
a701d44d25 chore(deps): update dependency hosted-git-info to 2.8.9 [security] (#2138)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 15:12:18 +01:00
renovate[bot]
4a67d1c679 chore(deps): update actions/setup-node action to v3 (#2154)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-03 15:00:35 +01:00
Renovate Bot
450ecedad4 fix(deps): update dependency webtorrent to v1.8.4 2022-03-03 12:28:01 +00:00
renovate[bot]
55ea243862 chore(deps): update dependency electron to v15.4.0 (#2150)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 12:42:00 +01:00
renovate[bot]
319a27399e chore(deps): update dependency tape to v5.5.2 (#2147)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 12:40:30 +01:00
renovate[bot]
f532d15222 chore(deps): update actions/checkout action to v3 (#2152)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-03 12:37:01 +01:00
renovate[bot]
3b4a2da2be fix(deps): update dependency music-metadata to v7.12.2 (#2151)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-03 12:31:12 +01:00
renovate[bot]
c464074c92 fix(deps): update dependency webtorrent to v1.8.3 (#2149)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 12:09:43 +01:00
renovate[bot]
4945ba9073 fix(deps): update dependency @electron/remote to v2.0.5 (#2148)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-03 12:07:52 +01:00
renovate[bot]
a4b7cfea3f chore(deps): update babel monorepo (#2146)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 10:40:33 +01:00
renovate[bot]
d0ed20f10b fix(deps): update dependency chokidar to v3.5.3 (#2121)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 10:36:42 +01:00
renovate[bot]
8964d9f349 fix(deps): update dependency iso-639-1 to v2.1.13 (#2081)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 10:34:57 +01:00
renovate[bot]
e1525a64fc chore(deps): update dependency open to v8.4.0 (#2076)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 10:29:09 +01:00
Christopher Toth
3a0415578a fix(accessibility): Fix roles and add correct label for navigation controls (#2143)
* Accessibility: Fix roles and add correct label for navigation controls

This makes screen readers properly indicate the control name, type, and state for the Back, Forward, and Add buttons.

* Update src/renderer/components/header.js

Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 10:25:54 +01:00
dependabot[bot]
3dc023828d build(deps): bump nanoid from 3.1.23 to 3.3.1 (#2144)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.23 to 3.3.1.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.23...3.3.1)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-03 03:23:01 +01:00
dependabot[bot]
3d01225b6d build(deps): bump shelljs from 0.8.4 to 0.8.5 (#2145)
Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.4 to 0.8.5.
- [Release notes](https://github.com/shelljs/shelljs/releases)
- [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/shelljs/shelljs/compare/v0.8.4...v0.8.5)

---
updated-dependencies:
- dependency-name: shelljs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-03 03:13:08 +01:00
renovate[bot]
0feb97275c fix(deps): update dependency prop-types to v15.8.1 (#2104)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 03:01:40 +01:00
Alex Vale
036ac72e3c fix: remove erroneous playbackRate check (#2106)
* remove erroneous playbackRate check

A playback rate != 1 causes shouldHidePlayerControls to erroneously return false.

* Add change

Co-authored-by: Alex <alxmorais8@msn.com>
2022-03-03 01:53:23 +00:00
Renovate Bot
f097a1ed52 fix(deps): update dependency webtorrent to v1.8.1 2022-02-09 17:28:55 +00:00
Renovate Bot
16485f29c8 chore(deps): update dependency @babel/core to v7.17.2 2022-02-09 17:13:30 +00:00
Renovate Bot
0b2cb410bd fix(deps): update dependency music-metadata to v7.11.8 2022-02-04 01:29:14 +00:00
Renovate Bot
330b4adb0e chore(deps): update babel monorepo to v7.17.0 2022-02-04 01:17:08 +00:00
Renovate Bot
32818c60a4 chore(deps): update dependency electron to v15.3.6 2022-01-28 20:51:14 +00:00
Renovate Bot
a55d7e897d fix(deps): update dependency @electron/remote to v2.0.4 2022-01-28 18:52:06 +00:00
Renovate Bot
bd34106a31 fix(deps): update dependency webtorrent to v1.7.2 2022-01-28 00:31:23 +00:00
Renovate Bot
85fb83fb67 chore(deps): update dependency tape to v5.5.0 2022-01-27 21:03:52 +00:00
renovate[bot]
0cfce0ff26 fix(deps): update dependency simple-get to v4.0.1 (#2124) 2022-01-24 23:03:59 -05:00
Renovate Bot
65147eb53a chore(deps): update dependency @babel/core to v7.16.12 2022-01-23 16:22:43 +00:00
Renovate Bot
5b6d29efcb fix(deps): update dependency webtorrent to v1.7.1 2022-01-20 18:45:36 +00:00
Renovate Bot
c5c53646eb fix(deps): update dependency music-metadata to v7.11.7 2022-01-18 15:06:57 +00:00
Renovate Bot
0927cedfa0 fix(deps): update dependency webtorrent to v1.6.0 2022-01-18 00:41:33 +00:00
Renovate Bot
22962e9b7c chore(deps): update dependency tape to v5.4.1 2022-01-17 05:31:02 +00:00
Renovate Bot
13932b0567 fix(deps): update dependency music-metadata to v7.11.6 2022-01-14 09:42:12 +00:00
Renovate Bot
ed38a55711 fix(deps): update dependency webtorrent to v1.5.11 2022-01-14 03:46:52 +00:00
Renovate Bot
e55d9b6d15 chore(deps): update dependency electron to v15.3.5 2022-01-13 03:57:35 +00:00
Renovate Bot
e15ae42c32 fix(deps): update dependency webtorrent to v1.5.9 2022-01-12 19:09:47 +00:00
Renovate Bot
a71808445e chore(deps): update dependency @babel/cli to v7.16.8 2022-01-11 23:30:20 +00:00
Renovate Bot
b742b10553 chore(deps): update dependency depcheck to v1.4.3 2022-01-10 20:32:22 +00:00
Renovate Bot
e8e9314486 chore(deps): update babel monorepo to v7.16.7 2022-01-01 02:06:53 +00:00
Renovate Bot
0b16be24c1 chore(deps): update dependency tape to v5.4.0 2021-12-27 08:13:46 +00:00
Renovate Bot
a90df37123 chore(deps): update dependency electron to v15.3.4 2021-12-17 21:37:54 +00:00
Renovate Bot
fc7608fa87 chore(deps): update babel monorepo to v7.16.5 2021-12-15 00:20:04 +00:00
Renovate Bot
82a61e9da4 chore(deps): update dependency electron to v15.3.3 2021-12-02 00:33:53 +00:00
Renovate Bot
2d0c1d597f chore(deps): update dependency tape to v5.3.2 2021-11-17 08:31:43 +00:00
Renovate Bot
4a9566665e chore(deps): update dependency electron to v15.3.2 2021-11-17 02:41:06 +00:00
Renovate Bot
fad7a21287 chore(deps): update dependency @babel/eslint-parser to v7.16.3 2021-11-10 21:58:46 +00:00
Renovate Bot
7893da9426 chore(deps): update dependency nodemon to v2.0.15 2021-11-10 19:46:09 +00:00
Renovate Bot
f0ddc79fcc chore(deps): update dependency electron to v15.3.1 2021-11-09 19:56:29 +00:00
Renovate Bot
f92afd3f10 chore(deps): update babel monorepo to v7.16.0 2021-10-31 00:31:49 +00:00
Renovate Bot
9ce6c3b461 chore(deps): update dependency electron-osx-sign to v0.6.0 2021-10-30 00:38:05 +00:00
Renovate Bot
b404b44aa1 fix(deps): update dependency webtorrent to v1.5.8 2021-10-26 19:12:55 +00:00
Renovate Bot
8c38377e38 fix(deps): update dependency webtorrent to v1.5.7 2021-10-25 19:02:31 +00:00
Renovate Bot
eda83e858d chore(deps): update dependency electron to v15.3.0 2021-10-21 18:02:23 +00:00
Renovate Bot
a6639aadcf chore(deps): update dependency nodemon to v2.0.14 2021-10-20 14:33:30 +00:00
Renovate Bot
2cc62e26b3 chore(deps): update dependency electron to v15.2.0 2021-10-15 22:43:17 +00:00
Diego Rodríguez Baquero
e42a515199 fix: modernify code (#2068)
* fix: modernify code

* standard fix

* fixes
2021-10-11 18:23:09 -05:00
Diego Rodríguez Baquero
c36e43eaa3 fix: electron upgrade fixes (#2058)
* fix: electron upgrade fixes

* fix: electron remote

* fixes

* fixes

* pin

* actually
2021-10-10 13:21:45 -05:00
Renovate Bot
0732bd93d2 chore(deps): update dependency electron to v15.1.2 2021-10-09 19:34:19 +00:00
Renovate Bot
f12027f836 chore(deps): update babel monorepo to v7.15.8 2021-10-07 21:33:39 +00:00
Renovate Bot
d4dfaa6047 chore(deps): update dependency electron to v15.1.1 2021-10-05 23:46:19 +00:00
Renovate Bot
809d122d98 fix(deps): update dependency webtorrent to v1.5.6 2021-10-05 17:07:29 +00:00
Renovate Bot
6dbbf8b85b fix(deps): update dependency chromecasts to v1.10.2 2021-10-05 15:11:48 +00:00
Renovate Bot
3150c04ef4 chore(deps): update dependency electron to v15.1.0 2021-10-02 16:55:35 +00:00
Alex
770c641f1b refactor: ETA calculation (#2055)
* Refactor ETA calculation

* Update src/renderer/pages/player-page.js

Co-authored-by: Alex <alxmorais8@msn.com>

* Fix lint issue

Co-authored-by: Alex <alxmorais8@msn.com>
2021-10-02 15:11:45 +02:00
Renovate Bot
9c0ad29db9 chore(deps): update dependency nodemon to v2.0.13 2021-09-24 21:10:00 +00:00
renovate[bot]
add6b2e900 chore(deps): update dependency electron to v15 (#2051)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-09-23 10:41:07 -05:00
Renovate Bot
83c2e7d731 fix(deps): update dependency music-metadata to v7.11.4 2021-09-23 03:38:47 +00:00
Renovate Bot
29393b1803 chore(deps): update dependency electron-packager to v15.4.0 2021-09-23 03:33:12 +00:00
Renovate Bot
0ba4d3b831 chore(deps): update dependency electron-notarize to v1.1.1 2021-09-23 03:26:29 +00:00
Renovate Bot
872f60e161 chore(deps): update babel monorepo 2021-09-23 03:17:49 +00:00
Renovate Bot
65886933ee fix(deps): update dependency chromecasts to v1.10.1 2021-09-23 03:07:39 +00:00
Renovate Bot
f300d8af21 chore(deps): update dependency plist to v3.0.4 2021-09-23 02:57:06 +00:00
Renovate Bot
f87068a288 chore(deps): update dependency gh-release to v6.0.1 2021-09-23 02:45:16 +00:00
renovate[bot]
1916f4f1a3 chore(deps): update dependency electron to v13.4.0 (#1997)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-09-22 20:53:54 -05:00
renovate[bot]
b0d9b89451 chore(deps): update actions/setup-node action to v2 (#2052)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-09-22 20:51:12 -05:00
Benjamin Tan
0d512db6e2 Ensure that default file/protocol handlers are re-installed after updating. (#997)
Previously, they were only installed when the preference was changed.
This caused the handlers to point to non-existing files after updates
occurred and older versions were removed by Squirrel.

Closes #791, #911.
2021-09-22 20:11:53 -05:00
Vincenzo De Petris
24969c87f3 Fix ghost image showing on media player drag (#1922)
* fix: ghost image showing on media player drag

* fix: add missing space

* fix: access Image from window object
2021-09-22 20:10:30 -05:00
Adam Gotlib
5619f1805b Release media keys when opening external player (#1237) 2021-09-22 20:07:00 -05:00
Alex
e9d72ebb17 fix: resume torrents after deletion (#1974)
* fix: resume torrents after deletion

* Update torrent-list-controller.js

Co-authored-by: Diego Rodríguez Baquero <github@diegorbaquero.com>
2021-09-22 19:56:00 -05:00
Julen Garcia Leunda
c7647440ba build: add package Github Action (#1888)
* Add package Github Action

* Update package.yml

Co-authored-by: Diego Rodríguez Baquero <github@diegorbaquero.com>
2021-09-22 19:52:06 -05:00
Renovate Bot
86ba117e04 fix(deps): update dependency webtorrent to v1.5.5 2021-09-02 13:01:29 +00:00
Renovate Bot
27cf60d5dc fix(deps): update dependency webtorrent to v1.5.4 2021-08-25 23:15:05 +00:00
Renovate Bot
d06cddd94a fix(deps): update dependency webtorrent to v1.5.3 2021-08-20 19:17:40 +00:00
Renovate Bot
21ad84daf8 fix(deps): update dependency webtorrent to v1.5.1 2021-08-20 02:28:03 +00:00
Renovate Bot
c882e13889 fix(deps): update dependency webtorrent to v1.4.0 2021-08-17 20:38:57 +00:00
Renovate Bot
9a68910e31 fix(deps): update dependency webtorrent to v1.3.10 2021-08-16 14:21:14 +00:00
Agyant Tiwari
610abea1c3 Play/Pause video on click (#1878)
* Play/Pause video on click

* Fixing lint

* fix: use dispatcher

Co-authored-by: Alex <alxmorais8@msn.com>
2021-08-16 13:47:53 +02:00
Renovate Bot
b132fb73df chore(deps): update dependency tape to v5.3.1 2021-08-08 07:07:12 +00:00
Renovate Bot
eff52911c2 fix(deps): update dependency webtorrent to v1.3.9 2021-08-07 00:35:35 +00:00
Renovate Bot
3ff41a965f fix(deps): update dependency create-torrent to v5.0.1 2021-08-06 22:42:34 +00:00
Renovate Bot
8373fd17b8 fix(deps): update dependency webtorrent to v1.3.8 2021-08-06 05:07:39 +00:00
renovate[bot]
c531169a37 fix(deps): update dependency create-torrent to v5 (#2034)
* fix(deps): update dependency create-torrent to v5

* Update webtorrent.js

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Diego Rodríguez Baquero <github@diegorbaquero.com>
2021-08-05 20:54:37 -05:00
renovate[bot]
d00b9ba081 fix(deps): update dependency webtorrent to v1.3.7 (#2033)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-05 07:49:04 +00:00
renovate[bot]
1d644c82de fix(deps): update webtorrent (#2032)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-05 02:22:15 +00:00
renovate[bot]
d6308734ea fix(deps): update dependency chromecasts to v1.10.0 (#1991)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-04 15:09:59 -05:00
renovate[bot]
534c6719d1 chore(deps): update dependency electron-notarize to v1.0.1 (#2027)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-03 19:25:10 -05:00
renovate[bot]
4e4315160e fix(deps): update dependency chokidar to v3.5.2 (#1993)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-03 19:23:40 -05:00
renovate[bot]
25398030fa chore(deps): update babel monorepo (#1992)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-03 19:22:50 -05:00
renovate[bot]
b261a24bda chore(deps): update dependency open to v8.2.1 (#1996)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-03 19:21:56 -05:00
renovate[bot]
dcbe5851c2 fix(deps): update dependency drag-drop to v7.2.0 (#2004)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-03 19:21:28 -05:00
renovate[bot]
24ef81387e chore(deps): update dependency tape to v5.3.0 (#2023)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-03 19:16:45 -05:00
renovate[bot]
6a761c9a10 chore(deps): update dependency electron-packager to v15.3.0 (#2016)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-03 19:16:33 -05:00
Renovate Bot
8ac6e9effe chore(deps): update dependency nodemon to v2.0.12 2021-08-03 23:51:42 +00:00
Renovate Bot
fec063bd1d fix(deps): update dependency webtorrent to v1.3.4 2021-07-30 17:56:47 +00:00
Renovate Bot
8b76535ce2 fix(deps): update dependency webtorrent to v1.3.3 2021-07-25 02:21:47 +00:00
Renovate Bot
7072caf3ce fix(deps): update dependency webtorrent to v1.3.2 2021-07-24 20:29:48 +00:00
Renovate Bot
2ba8fd20ee fix(deps): update dependency webtorrent to v1.3.0 2021-07-24 00:22:23 +00:00
Renovate Bot
7f962109e9 fix(deps): update dependency webtorrent to v1.2.6 2021-07-23 20:45:48 +00:00
Renovate Bot
33e6b6565c fix(deps): update dependency create-torrent to v4.7.1 2021-07-22 16:38:52 +00:00
Diego Rodríguez Baquero
04cdc64a18 chore: add stale 2021-07-21 19:20:42 -05:00
Renovate Bot
452bc0e235 fix(deps): update dependency webtorrent to v1.2.5 2021-07-19 17:26:26 +00:00
Renovate Bot
607c5b8c11 fix(deps): update dependency webtorrent to v1.2.4 2021-07-13 22:53:36 +00:00
Renovate Bot
7ef697ed72 fix(deps): update dependency webtorrent to v1.2.3 2021-07-13 20:32:00 +00:00
Renovate Bot
86182417cc fix(deps): update dependency webtorrent to v1.2.2 2021-07-11 04:09:25 +00:00
Renovate Bot
200f385d23 fix(deps): update dependency webtorrent to v1.2.0 2021-07-10 00:09:01 +00:00
Renovate Bot
8a5140d93d fix(deps): update dependency webtorrent to v1.1.6 2021-07-08 22:26:41 +00:00
Renovate Bot
a5c2fa08a5 fix(deps): update dependency webtorrent to v1.1.5 2021-07-06 18:43:20 +00:00
Renovate Bot
d0198a4419 fix(deps): update dependency webtorrent to v1.1.4 2021-07-03 06:48:43 +00:00
Renovate Bot
de1b38e660 fix(deps): update dependency webtorrent to v1.1.3 2021-07-03 00:49:17 +00:00
Renovate Bot
50bf04c73a fix(deps): update dependency webtorrent to v1.1.0 2021-06-30 22:08:43 +00:00
Renovate Bot
1cfa101b90 fix(deps): update dependency webtorrent to v1.0.4 2021-06-30 07:13:21 +00:00
Renovate Bot
14e2775980 fix(deps): update dependency webtorrent to v1.0.2 2021-06-23 20:32:19 +00:00
Julen Garcia Leunda
8e86c855d3 Add armv7l arch support (#1901) 2021-06-12 12:50:48 -05:00
renovate[bot]
01b3772138 fix(deps): update dependency music-metadata to v7.8.6 (#1958)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-12 12:50:16 -05:00
renovate[bot]
1f84a81d97 chore(deps): update dependency electron to v13.1.0 (#1983)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-12 12:49:19 -05:00
renovate[bot]
12ab547bbc chore(deps): update babel monorepo to v7.14.5 (#1990)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-12 12:49:02 -05:00
Alex
ef4fc2d28d feat: paste torrent address with right click (#1989)
* feat: paste torrent address with right click

* Change right click to directly pasting
2021-06-09 23:55:40 +02:00
Feross Aboukhadijeh
d12fd7768a fix: Link to VLC using HTTPS (#1987)
May help with users who are man-in-the-middled and redirected to malicious websites.

See: https://github.com/webtorrent/webtorrent-desktop/issues/1908
2021-06-02 16:46:17 -07:00
Feross Aboukhadijeh
756480a69f chore: Update to Electron 13; Bump all deps (#1986)
* chore: Update to Electron 12; Bump all deps

* Fixes for Electron 12

* chore: Update to Electron 13
2021-06-02 08:21:35 -07:00
renovate[bot]
19a8e9aa3e chore(deps): update dependency gh-release to v6 (#1961)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Feross Aboukhadijeh <feross@feross.org>
2021-06-02 08:04:27 -07:00
renovate[bot]
23f20546f7 chore(deps): pin dependency electron to v11.1.0 (#1976)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 08:00:12 -07:00
renovate[bot]
7a2d1d1807 fix(deps): update dependency webtorrent to v0.118.0 (#1981)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:59:43 -07:00
renovate[bot]
0138a178c3 fix(deps): update dependency iso-639-1 to v2.1.9 (#1948)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:56:22 -07:00
renovate[bot]
ff6900d8f1 fix(deps): update dependency semver to v7.3.5 (#1949)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:54:49 -07:00
renovate[bot]
7217d45ff9 fix(deps): update react monorepo to v17.0.2 (#1950)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:53:00 -07:00
renovate[bot]
ba86131e4a chore(deps): update dependency electron-winstaller to v5 (#1960)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:52:21 -07:00
renovate[bot]
b57278f7f1 chore(deps): update dependency open to v8 (#1962)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:52:03 -07:00
renovate[bot]
c9136422b9 fix(deps): update dependency drag-drop to v7 (#1964)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:51:57 -07:00
renovate[bot]
34692dfd38 chore(deps): update babel monorepo (#1951)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:51:49 -07:00
renovate[bot]
a990b00b21 chore(deps): update dependency ws to 7.4.6 [security] (#1972)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:41:44 -07:00
Alberto Miranda
5f5e4e0ee2 Fix cannot set property 'enabled' of undefined (#1938) 2021-06-02 07:41:09 -07:00
Feross Aboukhadijeh
e9fa721ca8 Switch from Travis to GitHub Actions (#1978) 2021-06-02 07:33:48 -07:00
Feross Aboukhadijeh
7ba1510131 chore: Set node 16 and npm 7 as minimum versions (#1977) 2021-06-02 07:25:48 -07:00
renovate[bot]
057c192dc4 chore(deps): update dependency electron to 11.1.0 [security] (#1939)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-02 07:18:43 -07:00
renovate[bot]
b78966ae2e chore(deps): pin dependencies (#1965)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-31 22:02:24 -05:00
renovate[bot]
2c34f7af1c chore(deps): update dependency lodash to 4.17.21 [security] (#1941)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-24 08:09:49 -05:00
renovate[bot]
6885d23bde fix(deps): update dependency debounce to ^1.2.1 (#1947)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-24 08:09:16 -05:00
Diego Rodríguez Baquero
277010a1b8 Merge pull request #1956 from webtorrent/renovate_tape-5.x
chore(deps): update dependency tape to ^5.2.2
2021-05-24 08:08:00 -05:00
Diego Rodríguez Baquero
d0849accbb Merge pull request #1957 from webtorrent/renovate_chokidar-3.x
fix(deps): update dependency chokidar to ^3.5.1
2021-05-24 08:06:23 -05:00
Renovate Bot
080e8da591 fix(deps): update dependency chokidar to ^3.5.1 2021-05-24 04:34:36 +00:00
Renovate Bot
bf0b5f8cdb chore(deps): update dependency tape to ^5.2.2 2021-05-24 04:33:03 +00:00
Diego Rodríguez Baquero
6e151cd2a1 chore: add renovate config 2021-05-23 23:00:09 -05:00
Alex
62273fa6b2 Merge pull request #1932 from webtorrent/dependabot/npm_and_yarn/ua-parser-js-0.7.28
Bump ua-parser-js from 0.7.22 to 0.7.28
2021-05-07 11:31:34 +02:00
dependabot[bot]
00d0c3d828 Bump ua-parser-js from 0.7.22 to 0.7.28
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.22 to 0.7.28.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.22...0.7.28)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-06 21:11:35 +00:00
Feross Aboukhadijeh
61efbd6823 Merge pull request #1928 from RayBB/patch-1
update brew install command
2021-04-26 16:17:48 -07:00
Alex
e6a33fb53a Merge pull request #1905 from getsnoopy/fix-units
Fix formatting of measurement units
2021-04-14 09:57:26 +02:00
Alex
228d733539 Merge pull request #1927 from webtorrent/dependabot/npm_and_yarn/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1
2021-04-14 09:53:16 +02:00
Raymond Berger
83eb584422 update brew install command 2021-04-10 19:24:07 -10:00
dependabot[bot]
47e89813a0 Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-30 09:23:23 +00:00
Alex
f0894ced54 Merge pull request #1904 from webtorrent/dependabot/npm_and_yarn/ini-1.3.7
Bump ini from 1.3.5 to 1.3.7
2021-02-01 18:37:45 +01:00
Alex
5372887d46 Merge pull request #1920 from BlueManCZ/tray-fix
Improve libappindicator checking
2021-02-01 18:37:13 +01:00
BlueManCZ
f8d9567422 Change command 2021-01-31 22:21:13 +01:00
BlueManCZ
97b54744df Update comment 2021-01-31 11:19:44 +01:00
BlueManCZ
4a974a1d3d Look for libappindicator libraries instead of package 2021-01-31 11:12:18 +01:00
BlueManCZ
192560755c Merge branch 'master' of github.com:webtorrent/webtorrent-desktop 2021-01-31 10:40:05 +01:00
Varun Varada
d299704f57 Fix formatting of measurement units
There were some incorrectly formatted unit symbols and units names in
the app, so this commit fixes them per SI and ISO/IEC specifications.
2020-12-22 19:00:05 -06:00
dependabot[bot]
314e74d1ed Bump ini from 1.3.5 to 1.3.7
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-10 23:07:23 +00:00
Feross Aboukhadijeh
919af5bf15 Merge pull request #1217 from cezar-carneiro/show-progress-on-cast-screen 2020-11-26 01:44:32 +01:00
Feross Aboukhadijeh
d819acedd5 Merge pull request #1865 from BlueManCZ/patch-1 2020-11-25 22:33:25 +01:00
Feross Aboukhadijeh
54fcc5057b recursively update all deps 2020-11-24 12:02:08 -10:00
Feross Aboukhadijeh
cd5da2fe9b cross-zip@4 2020-11-24 12:00:00 -10:00
Feross Aboukhadijeh
1c79ffc720 bump deps; including electron ~11.0.3
Fixes: https://github.com/webtorrent/webtorrent-desktop/issues/1896
2020-11-24 11:56:17 -10:00
Feross Aboukhadijeh
a1a590fbba travis: disable cache
the cache keeps caching outdated versions which cause test failures
2020-11-22 16:32:36 -10:00
Feross Aboukhadijeh
606c02aef4 Remove extra-lint
- standard enforces 'let' and 'const' now
- I've never been a fan of the <100 chars per line rule since it violates the standard philosophy. it should be possible to write conformant code without needing a tool, so 'magic numbers' like 'no more than X lines/chars/statements' are not ideal
2020-11-22 16:29:59 -10:00
Feross Aboukhadijeh
4f68a8ad0b parse-torrent@9 2020-11-22 16:26:42 -10:00
Feross Aboukhadijeh
37d962fcf3 bump deps 2020-11-22 16:24:46 -10:00
Feross Aboukhadijeh
baf06461df Remove unused 'buble' dependency 2020-11-22 16:20:09 -10:00
Feross Aboukhadijeh
464e63fd92 standard (Fixes for PR #1818) 2020-11-22 16:12:04 -10:00
Feross Aboukhadijeh
2761e9c768 Merge pull request #1818 from surajrathod/master 2020-11-23 03:11:34 +01:00
suraj rathod
0efb5fa7ad Merge branch 'master' into master 2020-11-20 11:27:33 +05:30
Feross Aboukhadijeh
34947c355d standard@16 2020-11-18 12:38:20 -10:00
Feross Aboukhadijeh
958814f2ea use @babel/eslint-parser 2020-11-18 12:37:03 -10:00
Feross Aboukhadijeh
ec0c0c7870 Update package-lock.json 2020-11-18 12:30:13 -10:00
Feross Aboukhadijeh
ec0a1d1bb4 Merge pull request #1895 from webtorrent/electron11 2020-11-18 03:41:45 +01:00
Feross Aboukhadijeh
969d71afec Merge pull request #1698 from webtorrent/babel
Use babel instead of buble
2020-11-18 03:41:19 +01:00
Feross Aboukhadijeh
c1db01b936 Use babel instead of buble
This way we can target just the JSX tranformation. We don't use any other cutting edge JS features that require compilation, nor should we in the future.
2020-11-17 16:40:14 -10:00
Feross Aboukhadijeh
90670a4645 changelog: add link to security issue explanation 2020-11-17 15:42:11 -10:00
Feross Aboukhadijeh
b5fad54f4a Upgrade to Electron 11 2020-11-16 18:03:35 -10:00
Feross Aboukhadijeh
50718d3791 Merge pull request #1887 from webtorrent/fix_lintian_warnings
Fix lintian warnings
2020-11-09 22:58:14 +01:00
Julen Garcia Leunda
bd54a8c214 Fix lintian warnings 2020-11-06 17:13:22 +01:00
Feross Aboukhadijeh
0cd0edf4cc Merge pull request #1886 from webtorrent/throttling
Disable background throttling
2020-11-05 22:30:22 +01:00
Feross Aboukhadijeh
9e0d91f144 Disable background throttling
Reference: https://github.com/webtorrent/webtorrent-desktop/pull/1720#issuecomment-722011487

I actually think we want backgroundThrottling: false so our timers and intervals aren't throttled when the app is backgrounded.
2020-11-04 12:34:48 -10:00
Feross Aboukhadijeh
38d9c4c504 Merge pull request #1885 from webtorrent/bitfield
bitfield@4
2020-11-04 10:15:40 +01:00
Feross Aboukhadijeh
9e1721990d bitfield@4 2020-11-03 13:03:40 -10:00
Feross Aboukhadijeh
a20f82cc61 standard 2020-10-29 11:30:07 -10:00
Feross Aboukhadijeh
630ed76b8c standard 2020-10-28 09:52:07 -10:00
Feross Aboukhadijeh
1155cba4dd Merge pull request #1883 from webtorrent/deps 2020-10-28 19:18:16 +01:00
Feross Aboukhadijeh
2343181e1b Update package-lock.json 2020-10-28 08:15:53 -10:00
Feross Aboukhadijeh
9bb7330caa spectron@12 2020-10-28 08:14:10 -10:00
Feross Aboukhadijeh
3aae1614ea pngjs@6 2020-10-28 08:13:13 -10:00
Feross Aboukhadijeh
cdd308fc4a gh-release@4 2020-10-28 08:12:11 -10:00
Feross Aboukhadijeh
594d18e76e react 17 2020-10-28 08:11:10 -10:00
Feross Aboukhadijeh
50d772eb54 parse-torrent@8 2020-10-28 08:10:43 -10:00
Feross Aboukhadijeh
57ffb5e0c4 bump deps 2020-10-28 08:10:26 -10:00
Feross Aboukhadijeh
49ab4bd201 Merge pull request #1882 from webtorrent/render-deprecated 2020-10-28 19:07:03 +01:00
Feross Aboukhadijeh
7b06acb972 don't rely on deprecated return value from ReactDOM.render 2020-10-27 18:18:30 -10:00
Feross Aboukhadijeh
80b86b5693 standard 2020-10-21 13:50:42 -10:00
Diego Rodríguez Baquero
748b996522 Merge pull request #1875 from webtorrent/dependabot/npm_and_yarn/electron-10.1.2
Bump electron from 10.1.1 to 10.1.2
2020-10-20 10:51:26 -05:00
Julen Garcia Leunda
77877d4201 Merge pull request #1812 from dsernst/scrub-preview
Adds Youtube-style Scrubbing preview
2020-10-20 17:31:43 +02:00
dependabot[bot]
696fa4479c Bump electron from 10.1.1 to 10.1.2
Bumps [electron](https://github.com/electron/electron) from 10.1.1 to 10.1.2.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/master/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v10.1.1...v10.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-06 17:49:29 +00:00
Feross Aboukhadijeh
e0bb7562be Update package-lock.json 2020-09-03 11:36:21 -07:00
Feross Aboukhadijeh
8f20d9676b electron 10.1.1 2020-09-03 11:35:08 -07:00
BlueManCZ
b61a4e84f5 Remove Version entry 2020-08-29 14:19:36 +02:00
BlueManCZ
9c59157a98 Remove VERSION entry 2020-08-29 14:03:27 +02:00
Ivo Šmerek
5def2d3947 Fix StartupWMClass 2020-08-29 13:53:02 +02:00
Feross Aboukhadijeh
ce6326a869 Update RELEASE_PROCESS.md 2020-08-28 17:58:07 -07:00
Feross Aboukhadijeh
7e56550887 Update RELEASE_PROCESS.md 2020-08-28 17:51:12 -07:00
Feross Aboukhadijeh
aa42627d45 0.24.0 2020-08-28 17:19:47 -07:00
Feross Aboukhadijeh
bd6dc1a7c1 authors 2020-08-28 17:19:20 -07:00
Feross Aboukhadijeh
dfedbcea32 add 0.24.0 changelog 2020-08-28 17:18:42 -07:00
Feross Aboukhadijeh
d930b97b30 Merge pull request #1864 from webtorrent/update 2020-08-28 17:14:58 -07:00
Feross Aboukhadijeh
de7899c8f8 Update package-lock.json 2020-08-28 17:06:51 -07:00
Feross Aboukhadijeh
2f53c5e137 bump electron deps 2020-08-28 17:03:33 -07:00
Feross Aboukhadijeh
ff54f48abb bump deps 2020-08-28 17:00:29 -07:00
Feross Aboukhadijeh
a7766e8d3f fixes for capture-frame@4 2020-08-28 16:58:40 -07:00
Feross Aboukhadijeh
844e34e596 capture-frame@4 2020-08-28 16:58:36 -07:00
Julen Garcia Leunda
8515fee681 Merge pull request #1859 from webtorrent/add_m2ts_video_container
Add m2ts video container support
2020-08-17 21:58:32 +02:00
Borewit
10a1ee3a09 Merge pull request #1861 from webtorrent/update-music-metadata
Update music-metadata to version 7.0.2
2020-08-17 19:26:32 +02:00
Borewit
a1b2641130 Update music-metadata to version 7.0.2 2020-08-16 08:48:37 +02:00
Julen Garcia Leunda
bb55b3fab2 Add m2ts video container support 2020-08-12 23:06:14 +02:00
Julen Garcia Leunda
57d02aa316 Merge pull request #1850 from Hinara/patch-1
Fix #1807 (VLC does not start)
2020-07-31 17:11:07 +02:00
David Ernst
d3bfccaa6b Check for type === video in clearPreview() 2020-07-30 19:10:14 -07:00
Julen Garcia Leunda
3a92f596ff Merge pull request #1838 from webtorrent/update-trackers
Update trackers to be consistent
2020-07-30 18:31:43 +02:00
Julen Garcia Leunda
60b4365a76 Merge pull request #1840 from subins2000/fix-1839-no-keyevents-on-user-input
Fix #1839: Prevent keypress events on user input elements
2020-07-30 18:31:22 +02:00
Julen Garcia Leunda
cb0ad73c80 Merge pull request #1754 from hicom150/fix_windows_installer
Fix windows installer event handler
2020-07-30 18:30:59 +02:00
Julen Garcia Leunda
9c865050bb Merge pull request #1847 from webtorrent/update-music-metadata
Update music metadata
2020-07-30 18:30:08 +02:00
Hinara
0edeaa4d58 Accept null value as playerPath 2020-07-30 15:06:26 +02:00
Mathias Rasmussen
dfd1ca3a8e Merge pull request #1841 from webtorrent/update_loading_gif
Update loading GIF
2020-07-30 03:59:49 +02:00
Borewit
75d9939b98 Update dependency music-metadata to version 7 2020-07-29 18:00:29 +02:00
Hinara
f88d5426c9 Fix #1807
eb5a291c8b/src/renderer/lib/migrations.js (L219) cause external player to be a empty string instead of a null one
2020-07-25 02:39:09 +02:00
Borewit
1b7d4f09a9 Fix issue #1833, only pass metadata. Prevent MetadataCollector (from music-metadata) is serialized. 2020-07-21 23:00:22 +02:00
Alex
680376c4a6 Update loading.gif 2020-07-17 21:14:15 +02:00
Subin Siby
15cca44bd2 Fix #1839: Prevent keypress events on user input elements 2020-07-17 18:47:23 +05:30
Feross Aboukhadijeh
29d17870cb Update trackers to be consistent
Updated all .torrent files to include:

```
  "announce": [
    "udp://tracker.leechers-paradise.org:6969",
    "udp://tracker.coppersurfer.tk:6969",
    "udp://tracker.opentrackr.org:1337",
    "udp://explodie.org:6969",
    "udp://tracker.empire-js.us:1337",
    "wss://tracker.btorrent.xyz",
    "wss://tracker.openwebtorrent.com"
  ]
```

Removed dead trackers from the list.
2020-07-16 20:18:15 -07:00
Feross Aboukhadijeh
c150d22c97 Update package-lock.json 2020-07-16 10:14:43 -07:00
Feross Aboukhadijeh
4e0c1ed393 Update CHANGELOG.md 2020-07-15 23:04:06 -07:00
Feross Aboukhadijeh
c6240c3253 Update CHANGELOG.md 2020-07-15 22:38:58 -07:00
Feross Aboukhadijeh
5deb0d7e9a Merge pull request #1835 from webtorrent/osx-notarize
Add macOS Notarization
2020-07-16 07:28:56 +02:00
Feross Aboukhadijeh
803cce808b Add macOS Notarization
Fixes: https://github.com/webtorrent/webtorrent-desktop/issues/1675
2020-07-15 22:26:29 -07:00
Feross Aboukhadijeh
8e89c0962b 0.23.0 2020-07-15 20:13:51 -07:00
Feross Aboukhadijeh
50cc394a27 Update CHANGELOG.md 2020-07-15 20:13:51 -07:00
Feross Aboukhadijeh
0165d541b8 update dep 2020-07-15 20:13:51 -07:00
Feross Aboukhadijeh
5af36428fa Merge pull request #1834 from webtorrent/electron10 2020-07-16 05:01:40 +02:00
Feross Aboukhadijeh
48ad571a59 electron 10: re-enable remote module
Electron 10 disables the remote module by default. We've always tried to avoid using `remote` in WTD so there aren't many occurrances of it, but there are still a few places where we use it
2020-07-15 20:01:08 -07:00
Feross Aboukhadijeh
57a003d7c5 electron 10 crash reporter changes
Crash reporter doesn't need to be started in child processes, and in fact the functions are all deprecated. Instead start it from the main process, and the child processes will automatically be covered

Change of params to crashReporter.start() function
2020-07-15 19:44:34 -07:00
Feross Aboukhadijeh
5f5b13d7fe update to electron 10.0.0-beta.11 2020-07-15 19:42:08 -07:00
Feross Aboukhadijeh
ed3f3f11f6 update tape, pngjs deps 2020-07-15 19:24:08 -07:00
Feross Aboukhadijeh
0572997a44 Update RELEASE_PROCESS.md 2020-07-15 19:15:47 -07:00
Feross Aboukhadijeh
4ce10859e7 0.22.0 changelog formatting 2020-07-15 18:17:03 -07:00
Feross Aboukhadijeh
eb5a291c8b Update RELEASE_PROCESS.md 2020-07-15 18:02:30 -07:00
Feross Aboukhadijeh
4b6394d0d5 0.22.0 2020-07-15 17:55:28 -07:00
Feross Aboukhadijeh
6b075ebe83 update integration tests to pass 2020-07-15 17:55:02 -07:00
Feross Aboukhadijeh
9b4dea29f4 lock spectron to 11.0.0
11.1.0 contains breaking changes. See: https://github.com/electron-userland/spectron/issues/647
2020-07-15 16:38:55 -07:00
Feross Aboukhadijeh
a2539b15d5 Prep for 0.22.0 release
Supercedes: https://github.com/webtorrent/webtorrent-desktop/pull/1730

Thanks @hicom150 :)
2020-07-15 15:39:39 -07:00
Feross Aboukhadijeh
a0da514f33 Update package.js 2020-07-15 15:22:01 -07:00
Feross Aboukhadijeh
ff5ae7a054 authors 2020-07-15 14:47:28 -07:00
Feross Aboukhadijeh
27bbdcc465 fixes for Electron 9 2020-07-15 14:35:03 -07:00
Feross Aboukhadijeh
1f19ed9a2d update electron to 9; update related deps 2020-07-15 14:34:27 -07:00
Feross Aboukhadijeh
06ce97bf6c Remove mkdirp
Fixes: https://github.com/webtorrent/webtorrent-desktop/issues/1831
2020-07-15 14:03:27 -07:00
Feross Aboukhadijeh
5c67a561c1 update easy deps
Update deps

from ~70 down to 34 vulnerabilities (33 low, 1 moderate)
2020-07-15 14:03:26 -07:00
Feross Aboukhadijeh
1ee49318fc Merge pull request #1830 from webtorrent/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-07-15 13:47:25 -07:00
dependabot[bot]
cc06379ce8 Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-15 20:38:26 +00:00
Feross Aboukhadijeh
66ac79c709 Merge pull request #1828 from webtorrent/dependabot/npm_and_yarn/electron-7.2.4 2020-07-15 13:37:32 -07:00
dependabot[bot]
ad252c5fc6 Bump electron from 7.1.0 to 7.2.4
Bumps [electron](https://github.com/electron/electron) from 7.1.0 to 7.2.4.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/master/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v7.1.0...v7.2.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-07 00:30:18 +00:00
SurajRathod
ab9f86d6c9 added .vscode ignore 2020-06-06 17:47:38 +05:30
SurajRathod
583cecc661 selective import 2020-06-06 17:46:30 +05:30
Feross Aboukhadijeh
3d66d32597 Merge pull request #1806 from LinusU/object-shorthand 2020-05-17 12:43:29 -07:00
Feross Aboukhadijeh
e4962fca52 Merge pull request #1776 from webtorrent/greenkeeper/cross-zip-3.0.0 2020-05-10 11:34:04 -07:00
David Ernst
c4b318bd5f Clean up code and add better comments 2020-05-02 11:40:00 -07:00
David Ernst
d55acf9c12 Fix 'extra-lint' errors 2020-05-01 22:41:19 -07:00
David Ernst
24bc40c95e Fix preview border rendering 2020-05-01 22:06:05 -07:00
David Ernst
0716444be5 Working video scrubbing preview 2020-05-01 21:57:42 -07:00
David Ernst
85c0e99e16 Only show previews for video 2020-05-01 21:05:29 -07:00
David Ernst
0b57961ff7 Basic placeholder UI for scrubbing preview 2020-05-01 19:43:47 -07:00
Linus Unnebäck
81b3d64f42 Use object shorthand for properties 2020-04-26 15:14:36 +01:00
Borewit
cf0cf79440 Merge pull request #1795 from webtorrent/greenkeeper/music-metadata-6.3.6
Greenkeeper/music metadata 6.3.6
2020-03-16 18:11:00 +01:00
greenkeeper[bot]
a8e6796002 chore(package): update lockfile package-lock.json 2020-03-11 07:37:52 +00:00
greenkeeper[bot]
f77aff6adf fix(package): update music-metadata to version 6.3.6
Closes #1774
2020-03-11 07:37:48 +00:00
Borewit
7fd028e5fa Merge pull request #1787 from dsernst/dynamic-copyright-year
Automatically keep the copyright year up-to-date
2020-02-09 14:15:31 +01:00
David Ernst
74f1be5452 Remove unnecessary parentheses 2020-02-08 12:37:48 -08:00
Borewit
77317aadc4 Merge pull request #1582 from dsernst/feature/sort-file-alphanumerically-1185
Context menu option to sort files by name
2020-02-08 12:01:55 +01:00
David Ernst
8eb2fb8d5d Automatically keep the copyright year up-to-date 2020-02-05 16:15:17 -08:00
David Ernst
58de27c484 Merge branch 'master' into feature/sort-file-alphanumerically-1185 2020-02-05 09:22:51 -08:00
David Ernst
185f5ec30b Remove unnecessary natsort dep 2020-02-05 09:17:58 -08:00
greenkeeper[bot]
351e890db5 chore(package): update lockfile package-lock.json 2020-01-16 00:28:55 +00:00
greenkeeper[bot]
6f2a00c785 chore(package): update cross-zip to version 3.0.0 2020-01-16 00:28:51 +00:00
Borewit
6f2aa86229 Merge pull request #1772 from webtorrent/greenkeeper/music-metadata-6.1.0
Update music metadata 6.1.0
2020-01-08 20:02:17 +01:00
Borewit
38aef81476 Add additional audio file extensions: MPEG-Layer-2, Musepack, Matroska audio, WavePack 2020-01-08 19:58:48 +01:00
Borewit
9dc3bb4f03 Pass FileInfo into music-metadata.parseStream() according API v6.
Update metadata if music-metadata parse method resolves.
2020-01-06 12:01:38 +01:00
Borewit
9291449737 Sync lock file 2020-01-06 11:01:04 +01:00
greenkeeper[bot]
98ee69d696 chore(package): update lockfile package-lock.json 2020-01-05 16:41:17 +00:00
greenkeeper[bot]
c5d20d56ec fix(package): update music-metadata to version 6.1.0
Closes #1749
2020-01-05 16:41:12 +00:00
Julen Garcia Leunda
e341367583 Merge pull request #1239 from SharonGrossman/feat/remove-all-torrents
feat(delete-all-torrents): added remove all torrents capability to the Transfers menu
2020-01-05 16:29:01 +01:00
Diego Rodríguez Baquero
f92a4c3e7d Merge pull request #1764 from hicom150/remove_deep_equal
Remove deep-equal dependency
2020-01-04 13:07:57 -05:00
Julen Garcia Leunda
5b894df71b fix lint errors 2019-12-19 18:34:35 +01:00
Julen Garcia Leunda
4577670fa8 Merge branch 'master' into feat/remove-all-torrents 2019-12-19 18:26:29 +01:00
Julen Garcia Leunda
6cac1f84ec Merge pull request #1757 from webtorrent/greenkeeper/semver-7.0.0
Update semver to the latest version 🚀
2019-12-19 17:46:05 +01:00
Julen Garcia Leunda
3c6b189ae6 Remove deep-equal dependency 2019-12-19 17:23:17 +01:00
greenkeeper[bot]
570145b2a4 chore(package): update lockfile package-lock.json 2019-12-14 20:06:27 +00:00
greenkeeper[bot]
95f2356b4c fix(package): update semver to version 7.0.0 2019-12-14 20:06:23 +00:00
Julen Garcia Leunda
97426621bf Fix windows installer event handler 2019-12-12 22:48:13 +01:00
Julen Garcia Leunda
3b7a9f9830 Merge pull request #1751 from hicom150/update_to_dialog_sync_functions
Fix deprecated dialog callback functions
2019-12-12 22:09:58 +01:00
Julen Garcia Leunda
61b92efcda Merge pull request #1746 from webtorrent/greenkeeper/application-config-2.0.0
Update application-config to the latest version 🚀
2019-12-06 17:17:25 +01:00
hicom150
b3026dbd87 Merge remote-tracking branch 'upstream/master' into update_application_config 2019-12-06 16:58:15 +01:00
hicom150
2eea86d8eb fix application-config update 2019-12-06 16:48:53 +01:00
Julen Garcia Leunda
46fb391d6c Merge pull request #1752 from hicom150/architectures
Show all supported architectures
2019-11-28 18:47:35 +01:00
Julen Garcia Leunda
79b3597637 Merge pull request #1740 from webtorrent/greenkeeper/electron-7.1.0
Update electron to the latest version 🚀
2019-11-28 17:23:45 +01:00
Julen Garcia Leunda
32dc268891 Merge pull request #1735 from akaustav/master
Fixed Material UI Getting Started link
2019-11-28 17:23:01 +01:00
Julen Garcia Leunda
02975e6b0e Merge branch 'master' into greenkeeper/electron-7.1.0 2019-11-28 17:21:23 +01:00
Julen Garcia Leunda
83dd5e35a7 Fix integration tests 2019-11-28 17:13:37 +01:00
Julen Garcia Leunda
1d6054f78b Merge pull request #1750 from webtorrent/greenkeeper/nodemon-2.0.0
Update nodemon to the latest version 🚀
2019-11-28 16:39:56 +01:00
Julen Garcia Leunda
8a947a2f80 Merge pull request #1748 from webtorrent/update-music-metadata
Update music-metadata to 4.9.2
2019-11-28 16:35:29 +01:00
Julen Garcia Leunda
6c22f5f595 show all supported architectures 2019-11-28 16:22:23 +01:00
Julen Garcia Leunda
bea86b2a67 Merge pull request #1743 from gpatarin/issue-1741-savetorrentas
Fix for issue #1741 (Save Torrent File As...)
2019-11-26 17:11:25 +01:00
Julen Garcia Leunda
82f873270e Merge pull request #1737 from akaustav/fix-about-window
Fix: Increase height of 'About' window
2019-11-26 17:01:50 +01:00
Julen Garcia Leunda
2b8fa8b6fe fix review comments 2019-11-26 16:59:10 +01:00
Julen Garcia Leunda
38390405dc Merge remote-tracking branch 'upstream/master' into fix-about-window 2019-11-26 16:55:44 +01:00
Gael Patarin
f30343b821 Added buttonLabel as Save in the save dialog 2019-11-26 14:33:47 +01:00
hicom150
d738021285 Fix deprecated dialog callback functions 2019-11-23 00:03:20 +01:00
greenkeeper[bot]
08e32dde7e chore(package): update lockfile package-lock.json 2019-11-20 16:03:51 +00:00
greenkeeper[bot]
229321c6c5 chore(package): update nodemon to version 2.0.0 2019-11-20 16:03:40 +00:00
Borewit
3b65f2a4a7 Update music-metadata to 4.9.2 2019-11-19 21:13:56 +01:00
Diego Rodríguez Baquero
93b0e42609 Merge pull request #1739 from webtorrent/greenkeeper/depcheck-0.9.0
Update depcheck to the latest version 🚀
2019-11-18 12:36:00 -05:00
Diego Rodríguez Baquero
633cdb4a82 Merge pull request #1742 from webtorrent/greenkeeper/languagedetect-2.0.0
Update languagedetect to the latest version 🚀
2019-11-18 12:33:47 -05:00
greenkeeper[bot]
eda0e0f964 chore(package): update lockfile package-lock.json 2019-11-16 18:01:35 +00:00
greenkeeper[bot]
a850cf6635 fix(package): update application-config to version 2.0.0 2019-11-16 18:01:31 +00:00
Feross Aboukhadijeh
af8b9cf1f2 Update drag-drop to the latest version 🚀 (#1744)
Update drag-drop to the latest version 🚀
2019-11-12 16:13:31 -08:00
greenkeeper[bot]
cc6cc41ad3 chore(package): update lockfile package-lock.json 2019-11-13 00:07:46 +00:00
greenkeeper[bot]
8228036936 fix(package): update drag-drop to version 6.0.0 2019-11-13 00:07:40 +00:00
gpatarin
600646b6ce Making the save file dialog sync to ensure working properly on MacOS and others 2019-11-09 23:58:43 +01:00
greenkeeper[bot]
fc73d2e6fd chore(package): update lockfile package-lock.json 2019-11-05 23:41:49 +00:00
greenkeeper[bot]
616cef87cb fix(package): update languagedetect to version 2.0.0 2019-11-05 23:41:45 +00:00
greenkeeper[bot]
fdc65d6e51 chore(package): update lockfile package-lock.json 2019-11-05 14:45:08 +00:00
greenkeeper[bot]
be922ef7c1 chore(package): update electron to version 7.1.0 2019-11-05 14:45:03 +00:00
greenkeeper[bot]
7dd5385f15 chore(package): update lockfile package-lock.json 2019-11-01 22:46:37 +00:00
greenkeeper[bot]
a5a199b63c chore(package): update depcheck to version 0.9.0 2019-11-01 22:46:32 +00:00
Ameet Kaustav
ab129fa233 Fix: Increase Height of 'About' window even more 2019-10-31 01:17:09 -07:00
Mathias Rasmussen
35717e9dfb Fix config copyright path in about window 2019-10-30 12:58:30 +01:00
Ameet Kaustav
a881668b38 Fix: Increase height of 'About' window 2019-10-29 22:28:07 -07:00
Ameet Kaustav
7e92647360 Fixed Material UI Getting Started link 2019-10-29 19:54:03 -07:00
Feross Aboukhadijeh
0198d7493d spectron@9 2019-10-23 13:23:34 -07:00
Feross Aboukhadijeh
04d9bc8752 Bump versions 2019-10-23 13:21:49 -07:00
Feross Aboukhadijeh
3e5fdcf6b9 Fixes for Electron 7 2019-10-23 13:19:24 -07:00
Feross Aboukhadijeh
d538a23ad0 Update electron to the latest version 🚀 (#1729)
Update electron to the latest version 🚀
2019-10-23 13:19:12 -07:00
Feross Aboukhadijeh
8de77e3d11 use org .github repo 2019-10-23 11:37:03 -07:00
greenkeeper[bot]
985def222e chore(package): update lockfile package-lock.json 2019-10-22 06:04:33 +00:00
greenkeeper[bot]
9bf4576a50 chore(package): update electron to version 7.0.0 2019-10-22 06:04:22 +00:00
Feross Aboukhadijeh
a5c41c8313 Update open to the latest version 🚀 (#1724)
Update open to the latest version 🚀
2019-10-19 12:56:42 -07:00
Julen Garcia Leunda
11d04d52c1 Merge pull request #1720 from webtorrent/type-issues
Fix a few type errors
2019-10-18 11:26:25 +02:00
greenkeeper[bot]
cb5af1a396 chore(package): update lockfile package-lock.json 2019-10-15 18:45:03 +00:00
greenkeeper[bot]
8a487cff9e chore(package): update open to version 7.0.0 2019-10-15 18:44:44 +00:00
Julen Garcia Leunda
45920e065e Merge pull request #1712 from hicom150/change_audio_tracks
Add support for multiple audio tracks
2019-10-13 22:54:59 +02:00
hicom150
2d7b6c01d0 Merge branch 'master' into change_audio_tracks 2019-10-13 22:38:50 +02:00
Julen Garcia Leunda
c33acd0746 Merge pull request #1711 from hicom150/fix_codec_unsupported_detection
Improve codec unsupported detection
2019-10-13 22:12:56 +02:00
hicom150
f2c531baa4 use videoTracks and audioTracks to check unsupported codecs 2019-10-12 17:44:13 +02:00
hicom150
287bcfd703 Merge branch 'master' into fix_codec_unsupported_detection 2019-10-11 16:56:34 +02:00
Mathias Rasmussen
e71555e664 Fix type errors 2019-10-09 19:34:30 +02:00
Borewit
bc921c889e Merge pull request #1719 from webtorrent/update-music-metadata
Update music-metadata to v4.8.0
2019-10-08 10:54:08 +02:00
Julen Garcia Leunda
59ebe132db Merge pull request #1707 from hicom150/no_sandbox
Add --no-sandbox option to desktop files
2019-10-07 22:57:04 +02:00
hicom150
b47d335e86 Merge branch 'fix_codec_unsupported_detection' of github.com:hicom150/webtorrent-desktop into fix_codec_unsupported_detection 2019-10-07 22:08:46 +02:00
hicom150
f5e186dc67 Apply review changes 2019-10-07 22:06:50 +02:00
hicom150
3d85803df9 Improve codec unsupported detection 2019-10-07 22:06:50 +02:00
Borewit
5c64fb5d35 Update music-metadata to v4.8.0 2019-10-07 20:01:19 +02:00
hicom150
5280fc76e9 Merge branch 'master' into fix_codec_unsupported_detection 2019-10-07 17:54:41 +02:00
hicom150
d4a8d9a120 Apply review changes 2019-10-07 17:54:36 +02:00
Feross Aboukhadijeh
3fbf33b8ae remove borewit from funding.yml (per his request) 2019-10-06 14:52:23 -07:00
hicom150
0d27223f16 Add support for multiple audio tracks 2019-10-06 22:10:35 +02:00
Feross Aboukhadijeh
2c5fcab469 Merge pull request #1717 from pR0Ps/feature/verification-status
Report when files are being verified
2019-10-06 11:04:35 -07:00
Carey Metcalfe
8fc7150559 Report when files are being verified
When starting a torrent that is partially-downloaded, WebTorrent will
now report "Verifying" as it verifies the existing data on disk.

Fixes #1586
2019-10-03 02:22:12 -04:00
Feross Aboukhadijeh
89b53ffe96 Merge pull request #1716 from pR0Ps/master
Floor individual file percentages for display
2019-10-01 06:49:42 -07:00
Feross Aboukhadijeh
e990bc898d mono is needed to create setup.exe (#1715)
mono is needed to create setup.exe
2019-09-30 21:49:09 -07:00
Feross Aboukhadijeh
cb699b5fec readme: clarify windows homebrew instructions 2019-09-30 21:48:56 -07:00
Lucas
8a235c31e0 mono is needed to create setup.exe 2019-10-01 11:05:53 +13:00
Carey Metcalfe
8ee27b8ef4 Floor individual file percentages for display
Fixes #1713
2019-09-26 20:49:17 -04:00
hicom150
5023651e63 Improve codec unsupported detection 2019-09-25 22:24:56 +02:00
Julen Garcia Leunda
f65fc68179 Add --no-sandbox option to desktop files 2019-09-19 22:41:34 +02:00
Feross Aboukhadijeh
b9bcf747de Set default externalPlayerPath (#1702)
Set default externalPlayerPath
2019-09-19 12:58:42 -07:00
Feross Aboukhadijeh
88e7167b6c Add rpm package creation support (#1694)
Add rpm package creation support
2019-09-19 12:56:14 -07:00
Pieter Goetschalckx
f5ab39be8e Migrate config file 2019-09-19 15:22:49 +02:00
Julen Garcia Leunda
6e69e5d36e Set correct architecture for RedHat based packages 2019-09-18 20:44:14 +02:00
Feross Aboukhadijeh
9c4ca25615 Handle null paths in path selector (#1704)
Handle null paths in path selector
2019-09-18 11:22:33 -07:00
Mathias Rasmussen
d15f40b22d handle null paths 2019-09-18 19:41:26 +02:00
Pieter Goetschalckx
1befac8d0e Set default externalPlayerPath 2019-09-18 13:34:39 +02:00
Julen Garcia Leunda
cfa1ebbea7 Merge branch 'master' into add_package_rpm 2019-09-16 22:13:41 +02:00
Feross Aboukhadijeh
2a679dd6e8 Add linux arm64 build (Fixes for PR #1650) (#1696)
Add linux arm64 build (Fixes for PR #1650)
2019-09-15 14:50:14 -07:00
Feross Aboukhadijeh
fce5f00925 Add buble compiler target chrome version (#1695)
Add buble compiler target chrome version
2019-09-15 12:36:14 -07:00
Feross Aboukhadijeh
39d04c4b2a Add linux arm64 build (Fixes for PR #1650) 2019-09-15 12:30:30 -07:00
Feross Aboukhadijeh
5688e4538c introduce linux arm64 support (#1650)
introduce linux arm64 support
2019-09-15 12:19:15 -07:00
Julen Garcia Leunda
4e18d4091a Add rpm package creation documentation 2019-09-15 20:30:41 +02:00
Julen Garcia Leunda
cfde3ee85d Add buble compiler target chrome version 2019-09-15 19:34:16 +02:00
Julen Garcia Leunda
1c343cb4c1 Add rpm package creation support 2019-09-15 19:16:03 +02:00
Дамјан Георгиевски
e3e490daa5 Merge branch 'master' into master 2019-09-15 18:25:43 +02:00
Feross Aboukhadijeh
d7783749ca Update test-add-torrent.js 2019-09-14 17:06:28 -07:00
Feross Aboukhadijeh
b07fc8285f 0.21.0 2019-09-14 16:44:47 -07:00
Feross Aboukhadijeh
6379356612 authors 2019-09-14 16:44:38 -07:00
Feross Aboukhadijeh
ee3768faf8 Merge branch 'fix-windows' 2019-09-14 16:42:56 -07:00
Feross Aboukhadijeh
8ff65412a3 readme: screenshots: 2018 MacBook Pro 13" 2019-09-14 16:42:40 -07:00
Feross Aboukhadijeh
cb930bef0b test: Update screenshots 2019-09-14 16:42:01 -07:00
Feross Aboukhadijeh
6a76554563 Fix "EPERM: operation not permitted, open 'C:\Users\account\App… (#1693)
Fix "EPERM: operation not permitted, open 'C:\Users\account\AppData\Local
2019-09-14 16:35:12 -07:00
Feross Aboukhadijeh
e72005e239 Remove cp-file, use copyFileSync instead 2019-09-14 16:32:38 -07:00
Feross Aboukhadijeh
54b1832007 remove prepublish script
We don't publish the desktop app to npm anymore
2019-09-14 14:36:52 -07:00
Feross Aboukhadijeh
e714e00223 Fix Windows npm install
Like `appdmg`, `electron-installer-debian` cannot be installed on Windows.
2019-09-14 14:35:05 -07:00
Feross Aboukhadijeh
6989484e7c Add note about homebrew deps for debian build
Fixes: https://github.com/webtorrent/webtorrent-desktop/issues/1689
2019-09-14 14:08:01 -07:00
Feross Aboukhadijeh
53164a058d bump deps 2019-09-12 11:14:33 -07:00
Borewit
f84078c865 Merge pull request #1580 from webtorrent/ignore-intellij-project-files
Ignore JetBrains IntelliJ IDEA project files
2019-09-12 08:30:12 +02:00
Feross Aboukhadijeh
7eda218e82 Store magnet URI once torrent has been parsed (#1686)
Store magnet URI once torrent has been parsed
2019-09-11 19:44:06 -07:00
Feross Aboukhadijeh
54b0c23f11 Replace nobin-debian-installer (#1682)
Replace `nobin-debian-installer`
2019-09-10 14:12:52 -07:00
Feross Aboukhadijeh
b34417b1fc add back removed log statement 2019-09-10 14:12:42 -07:00
Julen Garcia Leunda
40005351b5 Merge branch 'master' into replace_nobin_debian_installer 2019-09-10 23:05:39 +02:00
Julen Garcia Leunda
6255ccd253 Resolve requested changes 2019-09-10 22:54:34 +02:00
Feross Aboukhadijeh
4a10a2dfd1 Merge pull request #1679 from webtorrent/then
Use promise then() instead of catch() with callbacks
2019-09-10 11:05:12 -07:00
Feross Aboukhadijeh
992ab0bf10 electron-osx-sign@0.4.13 2019-09-10 10:33:53 -07:00
Feross Aboukhadijeh
83a2d090ff bump deps 2019-09-10 10:33:09 -07:00
Feross Aboukhadijeh
8133ce7b92 create-torrent@4.4.1 2019-09-10 10:31:32 -07:00
Feross Aboukhadijeh
6a07f705f7 Update package-lock.json 2019-09-10 10:31:29 -07:00
Feross Aboukhadijeh
9455912d98 Update airplayer dependency (#1680)
Update airplayer dependency
2019-09-10 10:27:28 -07:00
Feross Aboukhadijeh
052e0a4a31 Added 'Release notes' and 'follow on twitter' options in help (#1683)
Added 'Release notes' and 'follow on twitter' options in help
2019-09-10 09:59:22 -07:00
Chandan Chowdary Bhagam
b8b1ddc138 Added 'Release notes' and 'follow on twitter' options in help 2019-09-10 09:17:28 +00:00
Feross Aboukhadijeh
a92fee3532 Auto populate 'Magnet Link' field with the address from clipboa… (#1681)
Auto populate 'Magnet Link' field with the address from clipboard
2019-09-09 18:46:29 -07:00
Julen Garcia Leunda
aff402f6e0 Replace nobin-debian-installer 2019-09-09 22:52:26 +02:00
Daniele Debernardi
a39691fdad Auto populate 'Magnet Link' field with the address from clipboard
Co-authored-by: Jon Koops <jonkoops@gmail.com>
2019-09-09 21:41:07 +02:00
hicom150
7eecf709f9 Update airplayer dependency 2019-09-08 11:51:11 +02:00
Feross Aboukhadijeh
dd13de5f65 Use promise then() instead of catch() with callbacks
Addresses my review here: https://github.com/webtorrent/webtorrent-desktop/pull/1419#pullrequestreview-285182318
2019-09-07 11:13:26 -07:00
Feross Aboukhadijeh
647eaf5af9 #1667 rename dependency opn to open (#1677)
#1667 rename dependency opn to open
2019-09-06 12:48:36 -07:00
Borewit
24394a9028 #1667 rename dependency opn to open 2019-09-06 20:46:29 +02:00
Feross Aboukhadijeh
e8835c3054 package: Remove npm dedupe
We don't want to modify the package-lock.json file at packaging time.
2019-09-06 00:12:08 -07:00
Feross Aboukhadijeh
2834cca9fa package: use npm ci, remove npm dedupe
`npm ci` does not modify the package-lock.json file, which is exactly what we want during packaging time. Otherwise, on Windows, npm will attempt to remove the mac-only dependencies like appdmg from package-lock.json.
2019-09-06 00:11:52 -07:00
Feross Aboukhadijeh
744e720c3b Ignore play() when called before init()
If an error occurs during startup, then it's possible that we'll try to call sound.play('ERROR') in the error handler, which will throw an exception. Instead, just don't play sounds in this case.
2019-09-05 23:59:32 -07:00
Feross Aboukhadijeh
a6dfd98717 Depend on webtorrent/nobin-debian-installer fork
Fixes: https://github.com/grunjol/nobin-debian-installer/pull/8
Fixes: https://github.com/grunjol/nobin-debian-installer/pull/7
2019-09-05 23:13:41 -07:00
Feross Aboukhadijeh
4550f09bf1 authors 2019-09-05 22:18:00 -07:00
Feross Aboukhadijeh
7289e32bea Update package-lock.json 2019-09-05 22:18:00 -07:00
Feross Aboukhadijeh
0bdbe66d96 Change electron-packager from callback to promises (#1673)
Change electron-packager from callback to promises
2019-09-05 22:17:42 -07:00
Feross Aboukhadijeh
e58e701e2c appdmg@0.6.0
Fixes: https://github.com/webtorrent/webtorrent-desktop/pull/1593
2019-09-05 22:10:05 -07:00
Feross Aboukhadijeh
ac9b79c8ca Merge pull request #1657 from hicom150/update_spectron
Fix integration test setup

Co-authored-by: null <23040076+greenkeeper[bot]@users.noreply.github.com>
Co-authored-by: Feross Aboukhadijeh <feross@feross.org>
2019-09-05 21:55:27 -07:00
Feross Aboukhadijeh
280fbc1e8d Merge branch 'master' into update_spectron 2019-09-05 21:54:05 -07:00
Feross Aboukhadijeh
a1d4f706f7 Change electron-packager from callback to promises (#1660)
Change electron-packager from callback to promises
2019-09-05 21:47:44 -07:00
Feross Aboukhadijeh
6e2ff6887d Merge pull request #1642 from webtorrent/remove-32
Stop shipping 32-bit binaries
2019-09-05 21:45:52 -07:00
Feross Aboukhadijeh
fe297d4c56 Fixes for PR #1654 2019-09-05 21:42:12 -07:00
Feross Aboukhadijeh
b16971491f Merge branch 'master' of https://github.com/jimmywarting/webtorrent-desktop into jimmywarting 2019-09-05 21:39:19 -07:00
Feross Aboukhadijeh
4b1866d521 electron-winstaller@4 2019-09-05 21:22:47 -07:00
Feross Aboukhadijeh
c539375adc update deps 2019-09-05 21:18:52 -07:00
Feross
ec8beda1ed Merge pull request #1666 from webtorrent/update-deps
Update dependencies (prep for release)
2019-09-05 12:18:55 -07:00
Feross Aboukhadijeh
cd96986445 package.json: bump deps to use latest version
This doesn't actually change package-lock.json in a meaningful way. But I want package.json to list the latest version.
2019-09-04 17:28:15 -07:00
Feross Aboukhadijeh
975e57d57b update castv2 dependency to fix npm audit warning
For: https://github.com/webtorrent/webtorrent-desktop/issues/1599
2019-09-04 16:58:08 -07:00
Feross Aboukhadijeh
08ad4f73f1 music-metadata@4.5.0 2019-09-04 16:54:16 -07:00
Feross Aboukhadijeh
c9f9c868df electron@6.0.7 2019-09-04 16:52:11 -07:00
Feross Aboukhadijeh
e7f8f4ccfc react@16.9.0, react-dom@16.9.0 2019-09-04 16:48:11 -07:00
Feross Aboukhadijeh
e1b0c85778 iso-639-1@2.1.0 2019-09-04 16:44:47 -07:00
Feross Aboukhadijeh
2148d98f3a electron-packager@14.0.5 2019-09-04 16:44:10 -07:00
Feross Aboukhadijeh
d06508fda6 create-torrent@4.3.0 2019-09-04 16:43:17 -07:00
Feross Aboukhadijeh
1f780fc307 npm dedupe 2019-09-04 16:42:52 -07:00
Feross Aboukhadijeh
0178e9dcb8 bitfield@3 2019-09-04 16:41:50 -07:00
Feross Aboukhadijeh
c170c247a9 cross-zip@2.1.6 2019-09-04 16:40:01 -07:00
Feross Aboukhadijeh
4bb3edbf8d deep-equal@1.1.0 2019-09-04 16:39:39 -07:00
Feross Aboukhadijeh
9dbae636ae nodemon@1.19.2 2019-09-04 16:36:46 -07:00
Feross Aboukhadijeh
b6234f7705 vlc-command@1.2.0 2019-09-04 16:36:06 -07:00
Feross Aboukhadijeh
101dd356ae webtorrent@>=0.107.6
To address minor xss vulnerability in http server. See: https://github.com/webtorrent/webtorrent/pull/1714
2019-09-04 16:35:43 -07:00
Feross Aboukhadijeh
f8e9db25cd funding: add borewit 2019-09-04 14:50:14 -07:00
Feross Aboukhadijeh
8e33507839 fixed by bumping babel-eslint 2019-08-28 22:49:36 -07:00
Feross Aboukhadijeh
4c4c3508c2 Merge pull request #1664 from webtorrent/greenkeeper/drag-drop-5.0.1
Greenkeeper/drag drop 5.0.1
2019-08-28 13:14:07 -07:00
greenkeeper[bot]
a7ec8ba04e chore(package): update lockfile package-lock.json 2019-08-28 20:04:57 +00:00
greenkeeper[bot]
b5dce351b0 fix(package): update drag-drop to version 5.0.1
Closes #1663
2019-08-28 20:04:53 +00:00
Julen Garcia Leunda
59fa19d7ad Change electron-packager from callback to promises 2019-08-20 23:22:11 +02:00
hicom150
619e5249ac Fix integration test setup 2019-08-19 16:27:21 +02:00
greenkeeper[bot]
48340010b1 chore(package): update lockfile package-lock.json 2019-08-19 16:27:21 +02:00
greenkeeper[bot]
4599752d9d chore(package): update spectron to version 8.0.0
Closes #1609
2019-08-19 16:27:21 +02:00
Feross Aboukhadijeh
5eec89cd42 Merge pull request #1658 from webtorrent/music-metadata-v4.3
Update dependency music-metadata to v4.3.0
2019-08-18 14:22:10 -07:00
Borewit
dcecab2d9f Update dependency music-metadata to v4.3.0; improve decoding MPEG-4-ADTS/AAC. 2019-08-18 22:09:02 +02:00
Feross Aboukhadijeh
767d31ebd8 Merge pull request #1498 from webtorrent/cleanup-preferences
Cleanup preferences page
2019-08-18 12:30:50 -07:00
Feross Aboukhadijeh
1ece5de579 add discord link 2019-08-18 10:16:20 -07:00
Mathias Rasmussen
f34a79df36 path-selector: display path instead of name 2019-08-17 22:23:05 +02:00
Mathias Rasmussen
f231c54886 clean up preferences-page 2019-08-17 22:23:05 +02:00
Jimmy Wärting
56f0826928 remove zero fill at the final place
supersede #1583
2019-08-16 22:27:37 +02:00
Feross Aboukhadijeh
caceeee238 Merge pull request #1583 from jimmywarting/patch-1
Removed zero-fill
2019-08-16 11:49:38 -07:00
Feross Aboukhadijeh
afd3b84b40 Fixes for PR #1648 2019-08-15 13:10:39 -07:00
Feross Aboukhadijeh
4b927832c2 Merge pull request #1648 from hicom150/pass_title_metadata
Pass title metadata in media url
2019-08-15 13:08:59 -07:00
Feross Aboukhadijeh
c5679d546e chokidar@3
Fixes: https://github.com/webtorrent/webtorrent-desktop/pull/1576
2019-08-15 13:00:40 -07:00
hicom150
097fa9cc7c Add file name to state 2019-08-15 12:06:22 +02:00
Feross Aboukhadijeh
6f1a94359f standard 2019-08-14 17:52:55 -07:00
Feross Aboukhadijeh
44e2f366ce standard 2019-08-14 17:09:07 -07:00
Feross Aboukhadijeh
cf6b46364c standard 2019-08-14 17:07:33 -07:00
Дамјан Георгиевски
b8f7880a78 introduce linux arm64 support 2019-08-15 01:50:47 +02:00
Feross Aboukhadijeh
85c6816c23 standard 2019-08-14 16:29:25 -07:00
Feross Aboukhadijeh
0c1d7d6e0f standard 2019-08-14 16:21:14 -07:00
Feross Aboukhadijeh
3f073e083e standard 2019-08-14 16:13:07 -07:00
Feross Aboukhadijeh
6ce7e662c6 Merge pull request #1649 from webtorrent/greenkeeper/rimraf-3.0.0
Update rimraf to the latest version 🚀
2019-08-14 13:13:12 -07:00
greenkeeper[bot]
1e6c067139 chore(package): update lockfile package-lock.json 2019-08-14 18:18:26 +00:00
greenkeeper[bot]
f53038f6e9 fix(package): update rimraf to version 3.0.0 2019-08-14 18:18:02 +00:00
Feross Aboukhadijeh
c61234dd4c standard 2019-08-13 20:32:42 -07:00
Feross Aboukhadijeh
6336bbd6e3 use org-wide contributing file, move release process to new file 2019-08-13 15:15:43 -07:00
Feross Aboukhadijeh
649ac1900b use org-wide security file 2019-08-13 15:15:43 -07:00
Feross Aboukhadijeh
27e3d33f7d Use org-wide contributing file 2019-08-13 15:15:43 -07:00
hicom150
c29deab728 Pass title metadata in media url 2019-08-13 23:53:37 +02:00
Feross Aboukhadijeh
3a4ba6af37 Merge pull request #1641 from webtorrent/electron-6
Update to Electron 6
2019-08-13 13:05:56 -07:00
Feross Aboukhadijeh
e49d80fb17 Merge pull request #1646 from hicom150/fix_clean
Fix uninstall handlers in Linux
2019-08-12 12:18:52 -07:00
hicom150
a37f00bc10 Fix uninstall handlers in Linux 2019-08-12 10:29:30 +02:00
Feross Aboukhadijeh
925d495b62 remove stray console.log 2019-08-09 15:04:07 -07:00
Feross Aboukhadijeh
809beb4f81 Remove consistent JSX tag closing check
StandardJS is getting this rule in standard v14.
2019-08-09 15:03:06 -07:00
Feross Aboukhadijeh
0359451498 Update to Electron 6 2019-08-09 15:03:06 -07:00
Feross Aboukhadijeh
f6bd2cfcf4 Merge pull request #1643 from webtorrent/electron-5
Update to Electron 5
2019-08-09 15:02:49 -07:00
Feross Aboukhadijeh
9eecc2410c Stop shipping 32-bit binaries
For: https://github.com/webtorrent/webtorrent-desktop/issues/1637
2019-08-07 10:29:41 -07:00
Feross Aboukhadijeh
6cd8af91be Update to Electron 5 2019-08-07 10:24:31 -07:00
Feross Aboukhadijeh
7a3c1f0a10 Merge pull request #1634 from webtorrent/electron-packager
electron-packager@^14.0.4
2019-08-07 10:21:31 -07:00
Feross Aboukhadijeh
f2f3558cc4 Update lock.yml 2019-08-05 16:00:26 -07:00
Feross Aboukhadijeh
0dfaf6c1cf Update lock.yml 2019-08-05 15:58:04 -07:00
Feross Aboukhadijeh
a6ecba8e13 Merge pull request #1635 from webtorrent/changelog-v0.21
Update changelog for v0.21.0
2019-08-04 11:41:46 -07:00
Feross Aboukhadijeh
1a2c507d47 Update config.yml 2019-08-04 10:18:47 -07:00
Feross Aboukhadijeh
95b61826e0 Update stale.yml 2019-08-04 10:16:14 -07:00
Feross Aboukhadijeh
b1da72fde8 Merge pull request #1603 from refreex/original
Changed function name handlesoundNotificationsChange for handleSoundNotificationsChange
2019-08-04 09:37:47 -07:00
Feross Aboukhadijeh
b5e64a390a Merge pull request #1607 from webtorrent/music-metadata-4.1
Update music-metadata to version 4.2
2019-08-04 09:36:58 -07:00
Mathias Rasmussen
da829e0d69 resolve package-lock conflicts 2019-08-04 18:08:47 +02:00
Mathias Rasmussen
fd02908afa Update changelog for v0.21.0 2019-08-04 17:02:52 +02:00
Feross Aboukhadijeh
38092fa055 Update no-response.yml 2019-08-03 22:13:47 -07:00
Feross Aboukhadijeh
24a7e060f2 electron-packager@^14.0.4 2019-08-03 21:54:04 -07:00
Feross Aboukhadijeh
bd962d4d76 Create .npmrc
Helps primarily module developers (like me) who set their local .npmrc to package-lock=false
2019-08-03 21:23:49 -07:00
Feross Aboukhadijeh
a54dbc42e9 Update package-lock.json 2019-08-03 21:23:16 -07:00
Borewit
4c2381db26 Merge pull request #1590 from webtorrent/update-electron
Update to electron 4; it may not be perfect, but we have to move on
2019-07-30 21:44:22 +02:00
Feross Aboukhadijeh
91400ae31c standard 2019-07-27 18:33:46 -07:00
Feross Aboukhadijeh
c8f1e23b1a standard 2019-07-27 16:27:52 -07:00
Borewit
188f1cac8f Merge branch 'master' into update-electron
# Conflicts:
#	package-lock.json
#	package.json
2019-07-21 10:44:11 +02:00
Borewit
b216929dc4 Comply with max 100 chars per line lint rule. 2019-07-21 10:37:16 +02:00
Borewit
08008bb25c #1590 Improve single-instance start-up logic, remove duplicate code. 2019-07-21 10:19:36 +02:00
Feross Aboukhadijeh
c96f212726 Update package-lock.json 2019-07-20 23:12:03 -07:00
Feross Aboukhadijeh
80c1a61c33 Merge pull request #1605 from webtorrent/greenkeeper/babel-eslint-10.0.2
Update babel-eslint to the latest version 🚀
2019-07-20 22:54:41 -07:00
Borewit
7925e04834 Merge remote-tracking branch 'origin/greenkeeper/music-metadata-4.2.0' into music-metadata-4.1
# Conflicts:
#	package.json
2019-07-11 19:11:44 +02:00
Feross Aboukhadijeh
4da9da3d9a Merge pull request #1620 from webtorrent/greenkeeper/walk-sync-2.0.2
Greenkeeper/walk sync 2.0.2
2019-07-10 10:15:09 -07:00
Feross Aboukhadijeh
9916073a98 Merge pull request #1540 from RecoX/patch-1
Recommended readings to start working in the app.
2019-07-10 09:52:03 -07:00
Feross Aboukhadijeh
820776427b Merge pull request #1585 from webtorrent/greenkeeper/depcheck-0.8.0
Update depcheck to the latest version 🚀
2019-07-10 09:51:04 -07:00
greenkeeper[bot]
da76ff53ce chore(package): update lockfile package-lock.json 2019-07-09 22:43:22 +00:00
greenkeeper[bot]
9a657bdc10 chore(package): update walk-sync to version 2.0.2
Closes #1610
2019-07-09 22:43:17 +00:00
Feross Aboukhadijeh
df73ba2512 Merge pull request #1618 from webtorrent/greenkeeper/capture-frame-3.0.0
Update capture-frame to the latest version 🚀
2019-07-08 18:04:45 -07:00
greenkeeper[bot]
450237052c chore(package): update lockfile package-lock.json 2019-07-06 07:02:19 +00:00
greenkeeper[bot]
ffde524c85 fix(package): update capture-frame to version 3.0.0 2019-07-06 07:02:16 +00:00
Feross Aboukhadijeh
2618548fbe Merge pull request #1617 from webtorrent/greenkeeper/parse-torrent-7.0.0
Update parse-torrent to the latest version 🚀
2019-07-05 15:19:11 -07:00
Feross Aboukhadijeh
0471d141c4 standard 2019-07-05 15:06:27 -07:00
greenkeeper[bot]
8b31c0376c chore(package): update lockfile package-lock.json 2019-07-05 21:45:43 +00:00
greenkeeper[bot]
10c23104a1 fix(package): update parse-torrent to version 7.0.0 2019-07-05 21:45:40 +00:00
Feross Aboukhadijeh
9ed3533225 standard 2019-07-05 14:26:38 -07:00
Recox
97342951d0 Merge branch 'master' of https://github.com/webtorrent/webtorrent-desktop into original 2019-07-02 21:06:01 +12:00
Feross Aboukhadijeh
46d50ccda2 Merge pull request #1612 from webtorrent/greenkeeper/create-torrent-4.0.0
Update create-torrent to the latest version 🚀
2019-06-28 17:09:39 -07:00
greenkeeper[bot]
290027b5c5 chore(package): update lockfile package-lock.json 2019-06-28 23:57:54 +00:00
greenkeeper[bot]
f6129444f7 fix(package): update create-torrent to version 4.0.0 2019-06-28 23:57:51 +00:00
greenkeeper[bot]
57189c0d06 fix(package): update music-metadata to version 4.2.0
Closes #1611
2019-06-27 20:29:31 +00:00
Borewit
0aaeea4d3c Update music-metadata to version ^4.1.1 and adapt to API changes 2019-06-18 20:40:59 +02:00
Borewit
f59a54782a Fix processing arguments from second instance 2019-06-17 21:05:37 +02:00
greenkeeper[bot]
4156a3f546 chore(package): update lockfile package-lock.json 2019-06-17 14:22:03 +00:00
greenkeeper[bot]
13edc02440 chore(package): update babel-eslint to version 10.0.2 2019-06-17 14:21:56 +00:00
Borewit
cb4fa201f0 Merge pull request #1597 from dsernst/electron-4-detached-devtools
Electron 4: Migrate .openDevTools() for new 'detach' pattern
2019-06-17 09:36:49 +02:00
Recox
1905f8fec9 Changed function name handleSoundNotificationsChange 2019-06-11 21:32:19 +12:00
Borewit
d45e3f3e7b Merge pull request #1596 from dsernst/electron-4-titlebar
Fix hiddenInset (Mac) titlebar style for electron 4
2019-06-04 07:01:49 +02:00
David Ernst
a039cbf348 Electron 4: Migrate .openDevTools() for new 'detach' pattern 2019-06-03 21:52:06 -07:00
David Ernst
c5a21bddfd Fix hiddenInset (Mac) titlebar style for electron 4 2019-06-03 20:47:05 -07:00
Borewit
c204b7a82c onAppOpen seems to be no longer in use. 2019-05-15 19:54:13 +02:00
Borewit
f475975b40 Adjust single instance call to comply with Electron 4 2019-05-15 19:43:14 +02:00
Borewit
d3031ecff2 Merge branch 'greenkeeper/electron-4.0.0' into update-electron
# Conflicts:
#	package.json
2019-05-15 17:31:37 +02:00
Borewit
9a0a2111d3 Merge pull request #1309 from bilelmoussaoui/patch-2
Fix the desktop file
2019-05-12 20:18:20 +02:00
Borewit
d0053dc2f4 Merge pull request #1536 from adriantombu/feature/sound-option
Toggle the sound notifications
2019-05-12 20:03:14 +02:00
greenkeeper[bot]
60e9bf461c chore(package): update lockfile package-lock.json 2019-05-08 14:07:18 +00:00
greenkeeper[bot]
6add81add8 chore(package): update depcheck to version 0.8.0 2019-05-08 14:07:11 +00:00
Jimmy Wärting
a16b5b5add Removed zero-fill
sets the version the same way webtorrent dose

f8923a66a8/index.js (L27-L29)
2019-05-08 13:55:34 +02:00
David Ernst
aab918ff1a Fix lint error (Line >100 chars) 2019-05-06 16:23:50 -07:00
David Ernst
ca97413b76 Update package-lock.json for natsort 2019-05-06 16:00:23 -07:00
Borewit
8b0b5f3738 Merge pull request #1579 from dsernst/playback-hotkeys-hicom150
Add YouTube style hotkeys. Closes #1395
2019-05-06 19:40:07 +02:00
David Ernst
07816f7470 Add f=fullscreen hotkey 2019-05-06 09:58:47 -07:00
Borewit
28bf22a710 Ignore JetBrains IntelliJ IDEA project files 2019-05-05 11:39:25 +02:00
David Ernst
2466b2e0a6 Add context menu option to toggle Sort by Name 2019-05-04 22:39:27 -07:00
David Ernst
2dc2953831 Merge branch 'master' into feature/sort-file-alphanumerically-1185 2019-05-04 22:03:59 -07:00
David Ernst
103a5026f9 Add YouTube style hotkeys. Closes #1395 2019-05-04 21:04:41 -07:00
Borewit
c7ef42a65e Merge pull request #1573 from webtorrent/small-updates
Small updates
2019-04-28 21:55:58 +02:00
Alberto Miranda
ccaf73a477 Added missing dots for supported media extensions. 2019-04-28 16:00:43 -03:00
Borewit
dae57b5507 Merge remote-tracking branch 'origin/greenkeeper/opn-6.0.0' into small-updates 2019-04-25 20:05:58 +02:00
Borewit
5dc1ffb70e Merge branch 'pr-1562' into small-updates 2019-04-25 19:55:37 +02:00
Borewit
1dc500d4f3 Merge branch 'pr-1546' into small-updates 2019-04-25 19:54:25 +02:00
Borewit
d9bb625534 Merge remote-tracking branch 'origin/greenkeeper/depcheck-0.7.2' into small-updates
# Conflicts:
#	package-lock.json
2019-04-25 19:51:29 +02:00
Borewit
47cb6b9d5b Merge remote-tracking branch 'origin/greenkeeper/walk-sync-1.1.3' into small-updates 2019-04-25 19:48:34 +02:00
Borewit
b1eed5122a Merge remote-tracking branch 'origin/greenkeeper/semver-6.0.0' into small-updates 2019-04-25 19:47:39 +02:00
Borewit
97801c4fef Merge remote-tracking branch 'origin/greenkeeper/iso-639-1-2.0.5' into small-updates 2019-04-25 19:45:17 +02:00
Borewit
77d2abe04e Merge remote-tracking branch 'origin/greenkeeper/cp-file-7.0.0' into small-updates 2019-04-25 19:43:00 +02:00
Borewit
8b78e29827 Update lock file 2019-04-25 19:42:48 +02:00
Borewit
e6bfc57fc4 Add DSD audio media extensions for Sony DSF (DSD Stream Format) & Philips DSDIFF (Direct Stream Digital Interchange File Format
Related: https://github.com/Borewit/music-metadata/releases/tag/v3.6.0
2019-04-25 08:46:06 +02:00
Borewit
7facf4f6a2 Update music-metadata tp version 3.6.1 2019-04-25 08:45:53 +02:00
Borewit
79c25ffb33 Update lock file 2019-04-24 19:56:57 +02:00
greenkeeper[bot]
24dca39177 chore(package): update lockfile package-lock.json 2019-04-19 13:41:01 +00:00
greenkeeper[bot]
d209c02886 fix(package): update cp-file to version 7.0.0 2019-04-19 13:40:56 +00:00
David Ernst
c3315ab0fc Remove reference to (BETA) in test comments 2019-04-07 00:08:19 -05:00
David Ernst
109803e802 Remove '(BETA)' from app window title 2019-04-06 23:51:29 -05:00
greenkeeper[bot]
43ddbc7fd9 chore(package): update lockfile package-lock.json 2019-04-02 02:15:21 +00:00
greenkeeper[bot]
c8af0d3c1d fix(package): update iso-639-1 to version 2.0.5 2019-04-02 02:15:17 +00:00
greenkeeper[bot]
7db9e920de chore(package): update lockfile package-lock.json 2019-03-27 10:43:08 +00:00
greenkeeper[bot]
c8b726c0d1 fix(package): update semver to version 6.0.0
Closes #1559
2019-03-27 10:43:03 +00:00
greenkeeper[bot]
180acc15cd chore(package): update lockfile package-lock.json 2019-03-27 08:12:50 +00:00
greenkeeper[bot]
c3d261c74a chore(package): update opn to version 6.0.0 2019-03-27 08:12:46 +00:00
Feross Aboukhadijeh
0db89109a5 Remove NSP link 2019-03-25 22:23:43 -07:00
Diego Rodríguez Baquero
6ee0476799 Merge pull request #1531 from webtorrent/music-metadata-3.4.1
Update music-metadata to 3.5.2
2019-03-07 10:56:21 -05:00
Borewit
d86da0b0f7 Update music-metadata to 3.5.2, addressed sinificant performance issue handling MP3's. 2019-03-07 08:35:15 +01:00
greenkeeper[bot]
eb483ccd48 chore(package): update lockfile package-lock.json 2019-02-28 20:55:59 +00:00
greenkeeper[bot]
e7d5af5944 chore(package): update depcheck to version 0.7.2 2019-02-28 20:55:55 +00:00
David Ernst
0e3962e3f6 Update copyright for 2019 2019-02-23 00:09:42 -08:00
greenkeeper[bot]
992de4d80e chore(package): update lockfile package-lock.json 2019-02-05 03:46:35 +00:00
greenkeeper[bot]
85586d2dc5 chore(package): update walk-sync to version 1.1.3 2019-02-05 03:46:30 +00:00
Lucas
21ee5566a7 Update README.md 2019-01-31 11:48:37 +13:00
Lucas
429a376ccf Recommended readings to start working in the app.
As there is no too much information in the readme.md in how is this made, I think put a link of Electron and React to have an idea of the technologies used here is a good starter.
2019-01-31 11:41:31 +13:00
Mathias Rasmussen
a48c09e688 migrate sound option 2019-01-13 22:25:11 +01:00
Adrian Tombu
03de09fcaf Preference page cleaning 2019-01-13 11:17:46 +01:00
Adrian Tombu
97cf38e501 Toggle the sound notifications 2019-01-12 12:54:06 +01:00
Borewit
c24c99de36 Update music-metadata to 3.4.1 2019-01-01 17:33:08 +01:00
greenkeeper[bot]
a1ab889fca chore(package): update electron to version 4.0.0
Closes #1523
2018-12-21 00:36:42 +00:00
Borewit
170fd28c55 Merge pull request #1514 from webtorrent/greenkeeper/music-metadata-3.2.2
Greenkeeper/music metadata 3.3.0
2018-12-17 21:36:59 +01:00
Borewit
cdf5c5364a Merge branch 'master' into greenkeeper/music-metadata-3.2.2
# Conflicts:
#	package-lock.json
2018-12-16 17:15:16 +01:00
Borewit
2e85b4041f Updated music-metadata to v3.3.0. 2018-12-16 16:40:36 +01:00
Diego Rodríguez Baquero
a56e14f0a0 Merge pull request #1483 from webtorrent/update-material-ui
Update React and Material-UI
2018-12-12 14:28:46 -05:00
Mathias Rasmussen
16c3476960 update lockfile 2018-12-12 04:31:43 +01:00
Mathias Rasmussen
982ef3ae97 fix another unique key issue 2018-12-12 04:31:36 +01:00
Mathias Rasmussen
6057751e99 update react 2018-12-12 04:25:59 +01:00
Mathias Rasmussen
4c715111c5 fix react unique keys 2018-12-12 04:25:59 +01:00
Mathias Rasmussen
0d8e9792ff use react prop-types module 2018-12-12 04:25:59 +01:00
Mathias Rasmussen
ba5ccdb5cb update to material-ui 0.20.2
remove react-tap-event-plugin, as it is not needed anymore.
2018-12-12 04:25:59 +01:00
Mathias Rasmussen
579ca908c6 Merge pull request #1485 from webtorrent/update-auto-launch
Update auto-launch
2018-12-12 04:15:46 +01:00
Mathias Rasmussen
c7d7148132 update electron 2018-12-12 03:53:47 +01:00
Mathias Rasmussen
18ad5b0a01 Update dependencies 2018-12-12 03:48:01 +01:00
Mathias Rasmussen
cde3298920 update auto-launch 2018-12-12 03:37:06 +01:00
Borewit
20e3fb6f5f Add aif/aiff, spx (Speex) & mpc (Musepack) as audio file extensions.
Remove clashing .mp4 extension (with video) from audio.
2018-11-20 09:06:02 +01:00
greenkeeper[bot]
f0b87db248 chore(package): update lockfile package-lock.json 2018-11-19 19:50:48 +00:00
greenkeeper[bot]
2d90b29612 chore(package): update music-metadata to version 3.2.2 2018-11-19 19:50:44 +00:00
Borewit
81e6d7f3a7 Merge pull request #1506 from webtorrent/bnjmnt4n/playback-times
Ensure that the minutes field in playback indicator is zero-padded.
2018-11-06 18:37:01 +01:00
Benjamin Tan
a676a20054 Ensure that the minutes field in playback indicator is zero-padded.
The minutes field should be zero-padded as long as the playback media is longer than 9 minutes (including hours).

Fixes #1438.
2018-11-04 13:27:30 +08:00
Mathias Rasmussen
aef5669b76 Merge pull request #1497 from LinusU/prefer-const
Prefer const over let
2018-10-11 19:11:18 +02:00
Linus Unnebäck
ffb809bbca Prefer const over let 2018-10-10 21:21:29 +02:00
Mathias Rasmussen
60862ec01d refactor torrent-list-controller 2018-10-08 01:52:21 +02:00
Sharon Grossman
b7fedb1983 feat(delete-all-torrents): added remove all torrents capability to the Transfers menu 2018-10-08 01:52:21 +02:00
Borewit
fa7d917d0d Merge pull request #1494 from clujin/fix_empty_array_reduce
#1436: Fix bug empty array reduce in audio poster selection
2018-10-07 20:27:33 +02:00
Borewit
ab20dc42f1 #1426 Consistent error message if no suitable poster image could be found. 2018-10-07 20:15:12 +02:00
clujin
02fefd0a81 remove redundant error check 2018-10-07 10:42:42 -07:00
clujin
788e907502 remove semicolon 2018-10-06 19:14:03 -07:00
clujin
bb4eb523fe initial commit 2018-10-06 18:59:03 -07:00
Borewit
11b42e58c1 Merge pull request #1484 from sibiraj-s/patch-1
fix: replace dependency `open` with `opn`
2018-10-06 00:06:17 +02:00
Sibiraj
8dba9b93b6 chore: regenerate package-lock.json 2018-10-06 02:45:56 +05:30
Sibiraj
d4a564efb5 fix: replace dependency open with opn 2018-10-06 02:45:09 +05:30
Borewit
98ce4bb1fb Merge pull request #1042 from webtorrent/save-prefs
Save preferences immediately when changed
2018-10-05 21:03:49 +02:00
Borewit
0ec8809de1 Merge branch 'save-prefs' of https://github.com/webtorrent/webtorrent-desktop into save-prefs 2018-10-05 21:00:24 +02:00
Mathias Rasmussen
981ac464b5 update: remove unsaved 2018-10-05 20:58:54 +02:00
Dan Flettre
f04017a068 save preferences immediately when changed 2018-10-05 20:58:54 +02:00
Mathias Rasmussen
1a2b18fcbb update: remove unsaved 2018-09-28 17:45:03 +02:00
Dan Flettre
12d05b96a0 save preferences immediately when changed 2018-09-28 17:42:22 +02:00
Mathias Rasmussen
4ed4d8287f fix ci problems 2018-09-28 00:49:37 +02:00
Mathias Rasmussen
1239f7b871 lockfile 2018-09-27 21:47:32 +02:00
Mathias Rasmussen
8c2e29eaa2 update plist dependency 2018-09-27 21:41:17 +02:00
Borewit
9d060a66ec Merge pull request #1311 from bnjmnt4n/loading-spinner
Add back loading spinner for player page.
2018-09-27 19:55:17 +02:00
Borewit
06566d37c2 Asynchronous music-metadata updates while streaming (#1449)
* Ensure that default file/protocol handlers are re-installed after updating.

Previously, they were only installed when the preference was changed.
This caused the handlers to point to non-existing files after updates
occurred and older versions were removed by Squirrel.

Closes #791, #911.

* webtorrent/webtorrent-desktop#1340: Switch to async metadata updates.

* Use fat arrow

* Fix issue track number not displayed if total number of tracks is not defined (common.track.of === null).

* Add disk number in addition to track number.

* Update order of audio properties from: album, track, disk, format to track, disk, album, format

* #1340 Update music-metadata to 2.5.0, enabling async 'per' tag updates
#1452 Fix for playing some '.m4b' files

* #1340 Commented out the metadata event debug output.

* #1340 Remove line comment to get rid of max line length lint error

* Update music-metadata 2.6.0 to fix some async events are getting triggered.

* Return JSX block.

* Get rid of third parameter which is replaced by CSS capitalize

* Fixed error when value is undefined.

* Update music-metadata dependency to 2.6.1.

* fix(package): update music-metadata to version 3.1.0

Closes #1478

* Revert "Ensure that default file/protocol handlers are re-installed after updating."

This reverts commit 39145b2366.
2018-09-26 22:25:32 +02:00
greenkeeper[bot]
6a50fe7ced fix(package): update chokidar to version 2.0.4 (#1428) 2018-09-26 00:19:47 +02:00
Borewit
d40449f2ed Fix audio track number not displayed and adds audio disk (media) number (#1454)
* Fix issue track number not displayed if total number of tracks is not defined (common.track.of === null).

* Add disk number in addition to track number.

* Update order of audio properties from: album, track, disk, format to track, disk, album, format

* Return JSX block.

* Get rid of third parameter which is replaced by CSS capitalize

* Fixed error when value is undefined.
2018-09-25 23:51:45 +02:00
Feross Aboukhadijeh
23e1ab6f21 Merge pull request #1461 from webtorrent/greenkeeper/babel-eslint-9.0.0
Update babel-eslint to the latest version 🚀
2018-08-30 13:48:17 -07:00
Feross Aboukhadijeh
f23e52c699 Merge pull request #1462 from webtorrent/standard-12
standard
2018-08-28 19:41:59 -07:00
Feross Aboukhadijeh
10b4450214 standard 2018-08-28 19:38:25 -07:00
greenkeeper[bot]
4b639ae476 chore(package): update babel-eslint to version 9.0.0 2018-08-28 02:34:25 +00:00
Diego Rodríguez
77c497e984 Merge branch 'master' of github.com:webtorrent/webtorrent-desktop 2018-08-15 16:08:50 -05:00
Diego Rodríguez
a4ee465439 Update electron-winstaller and electron-osx-sign 2018-08-15 16:08:43 -05:00
Diego Rodríguez Baquero
b6890bc780 Merge pull request #1447 from webtorrent/greenkeeper/simple-get-3.0.3
Update simple-get to the latest version 🚀
2018-08-15 16:07:16 -05:00
Diego Rodríguez Baquero
109accdb54 Merge pull request #1450 from webtorrent/reuse-media-extensions
Reuse media extensions
2018-08-15 13:13:45 -05:00
Borewit
87491351cc Merge branch 'master' into reuse-media-extensions
# Conflicts:
#	src/renderer/lib/torrent-poster.js
2018-08-13 21:47:13 +02:00
Borewit
c2a38bd4e0 Ensure consistency between extension-categories, between torrent-player and torrent-poster.
Add additional audio-file-extensions.
2018-08-13 21:35:37 +02:00
greenkeeper[bot]
25282fc44a fix(package): update simple-get to version 3.0.3 2018-08-09 06:44:28 +00:00
Alberto Miranda
cbbc61d1e1 Merge pull request #1440 from webtorrent/Borewit-dependency/update-music-metadata
Small fixes added on top of #1435 by @Borewit.
Merging this one which is already approved and has those fixes.
We can continue making enhancements on top of this :)

Thanks guys!
2018-07-25 10:55:52 -03:00
Alberto Miranda
6ed7ec35a4 Fixed styling. 2018-07-22 16:58:28 -03:00
Alberto Miranda
a5d946fe83 Ensure we get proper audio metadata. 2018-07-22 16:17:34 -03:00
Borewit
cf8c1654e8 Add extension for MP4 audio book. 2018-07-21 14:10:23 +02:00
Borewit
715a7846f7 Bump version to 2.4.2: improve m4a handling. 2018-07-21 14:09:14 +02:00
Borewit
82ea41c1a1 Round bit-rate to kbps & sample rate to 1/10 of kHz.
Update music-metadata to 2.4.0.
2018-07-17 21:02:06 +02:00
Borewit
934f497118 Update music-metadata dependency: v2.0.1 => v2.3.2 2018-07-14 11:15:23 +02:00
Borewit
e0de30baff Merge pull request #1165 from janza/chromecast-subtitles
Add support for subtitles on Chromecast
2018-06-26 21:15:10 +02:00
Alberto Miranda
a1cfe8730b Merge pull request #1419 from Borewit/fix/issue-1417
Fix startup problems (fix for #1417)
2018-06-10 18:47:33 -03:00
Borewit
91fc7cab7c Merge pull request #1418 from webtorrent/greenkeeper/music-metadata-2.0.0
Update music-metadata dependency to version 2.0.1 🚀
2018-06-10 08:38:09 +02:00
Borewit
5cdb3d059c #1417: Simplify code
- use forEach instead of map (because return value was ignored)
- use Promise.all instead of parallel, because tasks are already Promise based. Fewer callbacks.
2018-06-09 14:54:24 +02:00
Borewit
b010dc28d2 Avoid promise resolve results are ending up as error objects in callback handler.
Reference: issue: webtorrent/webtorrent-desktop#1417
2018-06-07 21:53:58 +02:00
Borewit
9b51ba443a Update to version 2.0.1 fixing bug: Borewit/music-metadata#100
pull request: webtorrent/webtorrent-desktop#1418
2018-06-07 20:21:08 +02:00
greenkeeper[bot]
47c08cc8f8 fix(package): update music-metadata to version 2.0.0 2018-06-05 04:53:51 +00:00
Diego Rodríguez Baquero
c1329938f5 Merge pull request #1406 from Borewit/code-style/merge
Code style/merge
2018-06-03 18:46:26 -05:00
Borewit
2c07b1146c Merge remote-tracking branch 'origin/code-style/merge' into code-style/merge 2018-05-28 08:24:50 +02:00
Michael George Attard
fc51b230c4 rename parameter 2018-05-27 22:33:07 +02:00
Michael George Attard
4d5ee7887e Merge branch 'master' into feature/external-player.js-cleanup 2018-05-27 22:32:29 +02:00
Diego Rodríguez Baquero
ad63ec8a7b Merge pull request #1411 from webtorrent/is-private
If torrent is not private, leave private flag unset
2018-05-25 12:15:45 -05:00
Diego Rodríguez Baquero
2b0e2ca0fb Merge pull request #1410 from webtorrent/greenkeeper/parse-torrent-6.0.1
Update parse-torrent to the latest version 🚀
2018-05-25 11:53:02 -05:00
Feross Aboukhadijeh
217ca9a053 If torrent is not private, leave private flag unset
If torrent is not private, leave private flag unset. This ensures that the torrent info hash will match the result generated by other tools, including webtorrent-cli.
2018-05-25 01:10:40 -07:00
Feross Aboukhadijeh
6bf38611e8 fix bug, function needs to be invoked 2018-05-25 00:30:54 -07:00
Feross Aboukhadijeh
7251967ef5 style 2018-05-25 00:28:46 -07:00
Feross Aboukhadijeh
2c9b305721 style 2018-05-25 00:28:14 -07:00
greenkeeper[bot]
6eac7405fd fix(package): update parse-torrent to version 6.0.1 2018-05-25 07:23:55 +00:00
Feross Aboukhadijeh
4b51fb9255 e -> err 2018-05-25 00:20:39 -07:00
Feross Aboukhadijeh
a2e5390d30 ProBot No Response: tweak comment 2018-05-24 23:03:26 -07:00
Feross Aboukhadijeh
03a6ec3466 ProBot Welcome: tweak comments 2018-05-24 23:03:26 -07:00
Feross Aboukhadijeh
228d68b488 ProBot Request Info: tweak comment 2018-05-24 23:03:26 -07:00
Feross Aboukhadijeh
1af4ddb618 ProBot Lock: 90 days 2018-05-24 23:03:26 -07:00
Borewit
a30eedbec9 Update music-metadata to 1.1.0, which I expected to be part of #1396. 2018-05-23 21:00:55 +02:00
Borewit
2bc3ab6971 Merge branch 'code-style/main' into code-style/merge 2018-05-23 20:30:50 +02:00
Borewit
b1f5e7d7ec Merge branch 'code-style/external-player' into code-style/merge 2018-05-23 20:23:33 +02:00
Borewit
84d4e0f013 Issue #92: Merge with master & exclude some of the proposed changes. 2018-05-23 20:19:28 +02:00
Borewit
61648db0fc Merge pull request #1396 from webtorrent/greenkeeper/music-metadata-1.0.0
Update music-metadata to the latest version 🚀
2018-05-23 07:20:44 +02:00
Feross Aboukhadijeh
7b72dfafc2 ProBot Welcome: PR message tweak 2018-05-18 07:38:21 +02:00
Feross Aboukhadijeh
d3da2af8a1 Merge pull request #1325 from webtorrent/download-count-badge
add github download count badge
2018-05-17 11:32:40 +02:00
Borewit
55a60018b5 Merge pull request #1398 from Borewit/update-dependency/music-metadata-1.0.0
Fix code style
2018-05-16 21:41:16 +02:00
Borewit
6df718aa4a Fix code style 2018-05-16 21:31:48 +02:00
Borewit
a3d62de091 Merge pull request #1397 from Borewit/update-dependency/music-metadata-1.0.0
Add required changed to be able to merge music-metadata v1.0.0
2018-05-16 21:28:09 +02:00
Borewit
eb4007aa87 Update dependency music-metadata v1.0.0
Multiplicity of common.label & common.catalog-number number changed.
Added possibility to visualize multiple labels & catalog-number in pairs.
2018-05-16 21:23:34 +02:00
greenkeeper[bot]
4438c9c85f fix(package): update music-metadata to version 1.0.0 2018-05-16 18:25:57 +00:00
Feross Aboukhadijeh
c01380a897 Merge pull request #1394 from webtorrent/greenkeeper/capture-frame-2.0.0
Update capture-frame to the latest version 🚀
2018-05-15 04:28:54 -07:00
greenkeeper[bot]
ff7c053442 fix(package): update capture-frame to version 2.0.0 2018-05-15 11:16:40 +00:00
Feross Aboukhadijeh
4f342bb530 Merge pull request #1390 from webtorrent/greenkeeper/cp-file-6.0.0
Update cp-file to the latest version 🚀
2018-05-15 03:52:15 -07:00
greenkeeper[bot]
99a7c663b3 fix(package): update cp-file to version 6.0.0 2018-05-12 11:06:03 +00:00
Feross Aboukhadijeh
49f2bb2bdf ProBot Stale: never mark 'meta' issues stale 2018-05-10 17:52:28 -07:00
Feross Aboukhadijeh
9cfce5f72b Merge pull request #1386 from webtorrent/greenkeeper/drag-drop-4.1.0
Update drag-drop to the latest version 🚀
2018-05-10 04:01:48 -07:00
Feross Aboukhadijeh
5c8bd3422d travis 2018-05-10 03:18:42 -07:00
greenkeeper[bot]
670d6a0489 fix(package): update drag-drop to version 4.1.0 2018-05-10 10:15:22 +00:00
Feross Aboukhadijeh
45d0d94ab4 ProBot Welcome: tweak message 2018-05-10 00:21:50 -07:00
Feross Aboukhadijeh
4d864465d7 ProBot No Response: tweak message 2018-05-09 20:22:03 -07:00
Feross Aboukhadijeh
56327ca25e Merge pull request #1385 from webtorrent/probot
Add bots!
2018-05-09 20:17:52 -07:00
Feross Aboukhadijeh
0aef294df9 ProBot Stale 2018-05-09 20:15:38 -07:00
Feross Aboukhadijeh
7fb471feb1 ProBot No Response 2018-05-09 20:15:26 -07:00
Feross Aboukhadijeh
e2d2420aa2 ProBot Lock 2018-05-09 20:15:21 -07:00
Feross Aboukhadijeh
bcb3551a0a ProBot WIP 2018-05-09 20:15:15 -07:00
Feross Aboukhadijeh
20faf71338 ProBot Welcome 2018-05-09 20:15:11 -07:00
Feross Aboukhadijeh
63d866a1d2 ProBot Request Info 2018-05-09 20:15:05 -07:00
Feross Aboukhadijeh
6d9874601a travis: test node LTS only 2018-05-08 18:52:58 -07:00
Feross Aboukhadijeh
3cbf33b482 travis: Do full dependency install
I don't care about test time, I'd rather not have to keep this list up-to-date (Easier maintenance, e.g. this caused a failure because I just added babel-eslint and it doesn't get installed on travis and so standard fails)
2018-05-08 18:52:51 -07:00
Feross Aboukhadijeh
4b2a778708 Use babel-eslint with standard 2018-05-08 18:46:26 -07:00
Feross Aboukhadijeh
f5102c3558 Merge pull request #1260 from michaelgeorgeattard/master
Style clean up
2018-05-03 18:23:45 -07:00
Borewit
0fdc6b2d8b Merge pull request #1365 from webtorrent/readme-install
Improve install instructions
2018-05-02 20:28:00 +02:00
Feross Aboukhadijeh
e34c18b751 Merge pull request #1259 from jonkoops/update-chromecasts
Update 'chromecasts' dependency to latest version
2018-04-30 03:16:53 -07:00
Feross Aboukhadijeh
4f41f5968c Merge pull request #1368 from Borewit/feature/improve-audio-torrent-poster-selection
Prevent spectrogram being selected as the poster.
2018-04-29 20:20:11 -07:00
Feross Aboukhadijeh
b48d2b1610 Merge pull request #1371 from webtorrent/greenkeeper/music-metadata-0.11.2
Update music-metadata to the latest version 🚀
2018-04-29 20:18:11 -07:00
Feross Aboukhadijeh
ceb5c3f11c Merge pull request #1372 from webtorrent/greenkeeper/gh-release-3.2.1
Update gh-release to the latest version 🚀
2018-04-28 01:53:59 +02:00
greenkeeper[bot]
0585c515b3 chore(package): update gh-release to version 3.2.1 2018-04-27 17:03:20 +00:00
greenkeeper[bot]
e2074aeb31 fix(package): update music-metadata to version 0.11.2 2018-04-27 11:50:18 +00:00
Borewit
bc3cec1172 webtorrent/webtorrent-desktop#1368: Remove pointless factor. 2018-04-27 09:01:50 +02:00
Feross Aboukhadijeh
83e8bb1bd7 Merge pull request #1369 from webtorrent/new-mac-screenshots
Update mac screenshots for macOS 10.13.4
2018-04-26 19:04:14 -07:00
Feross Aboukhadijeh
88fb6d86bd Fix &nbsp; showing up in UI 2018-04-26 18:23:12 -07:00
Feross Aboukhadijeh
e8c39f2074 Fix screenshots further 2018-04-26 17:59:32 -07:00
Feross Aboukhadijeh
386b80a66f Update mac screenshots for macOS 10.13.4
It looks like font renderering changed slightly, so screenshots need to be updated
2018-04-26 17:25:32 -07:00
Feross Aboukhadijeh
fbb3fa6a27 0.20.0 2018-04-26 17:07:22 -07:00
Feross Aboukhadijeh
e10debed2f authors 2018-04-26 17:05:21 -07:00
Feross Aboukhadijeh
437dd3adb1 changelog 2018-04-26 16:35:32 -07:00
Feross Aboukhadijeh
e48191381e Merge pull request #1246 from webtorrent/greenkeeper/initial
Update dependencies to enable Greenkeeper 🌴
2018-04-26 16:29:14 -07:00
Feross Aboukhadijeh
abb7c72b84 undo most dependency updates 2018-04-26 16:29:04 -07:00
Feross Aboukhadijeh
20d9ea5a00 remove greenkeeper badge 2018-04-26 16:26:05 -07:00
Feross Aboukhadijeh
0da29e4eb2 Merge pull request #1317 from webtorrent/bugfix-1315
Bugfix 1315
2018-04-26 16:22:51 -07:00
Feross Aboukhadijeh
10f92c78eb Merge pull request #1316 from webtorrent/bugfix-1314
Bugfix 1314
2018-04-26 16:16:55 -07:00
Feross Aboukhadijeh
924dbeeebb Merge pull request #1366 from webtorrent/release/v0.20.0
Release/v0.20.0
2018-04-26 16:15:25 -07:00
Feross Aboukhadijeh
3c363595ce Fix for PR #1366 2018-04-26 16:14:56 -07:00
Feross Aboukhadijeh
695154099d Merge pull request #1367 from webtorrent/fix-linux-open-magnet
Fix external calls failing silently to open magnets
2018-04-26 15:56:06 -07:00
Borewit
8516da8a44 Prevent spectrogram being selected as the poster.
Related issue webtorrent/webtorrent-desktop#1332, as well pull request webtorrent/webtorrent-desktop#1334
2018-04-26 21:25:09 +02:00
Alberto Miranda
21edc05f19 Removed console log. 2018-04-26 15:04:23 -03:00
Alberto Miranda
cf729b0d31 Fix from leodutra: Fix external calls failing silently to open magnets #1168. 2018-04-26 15:00:58 -03:00
Alberto Miranda
74b806737d Removed already released change. 2018-04-26 11:10:13 -03:00
Alberto Miranda
88cbcc2959 0.20.0 2018-04-26 11:01:12 -03:00
Alberto Miranda
eab15876cc Updated changelog. 2018-04-26 11:00:58 -03:00
Alberto Miranda
c6a7b7c1d2 Merge pull request #1334 from Borewit/feature/improve-audio-torrent-poster-selection
Improve the poster selection for audio/music based torrent
2018-04-26 09:46:12 -03:00
Feross Aboukhadijeh
d76cd628cf Improve install instructions
Make the default install method stand out more than other options. Include a large link.
2018-04-25 19:45:22 -07:00
Feross Aboukhadijeh
7f2c91b230 Merge pull request #1364 from vitorgalvao/patch-1
README.md: add cask instructions
2018-04-25 19:35:53 -07:00
Vítor Galvão
dab0802a88 README.md: add cask install 2018-04-25 21:01:31 +01:00
Feross Aboukhadijeh
34f06267b8 Merge pull request #1166 from janza/fix-react-key-error
Add missing key to torrent status element
2018-04-24 16:03:30 -07:00
Alberto Miranda
99062175eb Merge pull request #1361 from webtorrent/music-metadata-fix
Displaying filename while music metadata is being downloaded.
2018-04-20 11:22:06 -03:00
Borewit
f47055c51b Adjust the filename score, size is probably more relevant. 2018-04-20 08:07:27 +02:00
Borewit
4752c2e689 Merge branch 'filename' into feature/improve-audio-torrent-poster-selection 2018-04-20 07:44:51 +02:00
Borewit
57c1a97ebf Update music-metadata dependency to v0.9.15. 2018-04-20 07:43:15 +02:00
Borewit
793ccebf0b Merge branch 'master' into feature/improve-audio-torrent-poster-selection 2018-04-20 07:38:53 +02:00
Alberto Miranda
c688313239 Displaying filename while music metadata is being downloaded. 2018-04-19 14:49:17 -03:00
Alberto Miranda
afd7f6ef0a Merge pull request #1240 from Borewit/music-metadata
Fix playing back audio files & reading music metadata
2018-04-19 14:38:39 -03:00
Terry Hau
cc78251846 Remove 'Always on top' from VLC player 2018-04-18 20:51:43 -07:00
Borewit
30693d7d16 Added some function comments.
Simplified code a bit.
2018-03-11 17:33:02 +01:00
Borewit
bd2fbe60db Pass file length parameter to music-metadata parser.
In some cases this is required to calculate the duration.
2018-03-11 17:13:56 +01:00
Borewit
a114a22770 #1332: Add album as likely name for an image containing the cover 2018-03-04 19:04:18 +01:00
Borewit
8d657d93e0 Fix code style 2018-03-04 18:49:59 +01:00
Borewit
5802cd3591 #1332: Add unit test for cover selection algorithm. 2018-03-04 18:44:14 +01:00
Borewit
2d17cfa7e1 #1332: Fix selection of poster.jpg|png|gif image 2018-03-04 18:43:12 +01:00
Borewit
cf2a3b8948 #1332: Improve cover image score algorithm:
- If filename score is equal, pick highest resolution
- Search for keyword occurrence in file name
2018-03-04 16:24:07 +01:00
Borewit
b89a74e6e9 #1332: Improve the poster selection for audio/music based torrent,
aiming to get the cover-art selected as the torrent poster.
2018-03-04 13:48:07 +01:00
Borewit
9b74602119 Fixed the win32 version of the test-integration/test-audio: updated screenshots to match new meta-data.
The format value has been to partially transparent because the bit-rate is not stable.
This is due to the fact these MP3 are encoded with Variable Bit-Rate (VBR),and they are lacking a "Xing" header, providing VBR encoding details.
Related to issue #1320 & #1240.
2018-03-04 10:45:30 +01:00
Borewit
34463d9084 Enforce music-metadata version which is able to deal with bad Ogg files 2018-03-03 14:44:50 +01:00
Borewit
3dcab9e78b Fix audio format metadata 2018-02-24 16:32:12 +01:00
Borewit
fce0003912 Add audio metadata standalone entries for: year & release information 2018-02-24 16:24:56 +01:00
Borewit
c720f65fee Update dependency music-metadata to version 0.9.5 2018-02-24 16:23:08 +01:00
Borewit
a725726e25 Fix formatting according 'standard' 2018-02-19 21:35:50 +01:00
Borewit
e5a64d9550 Add comments to metadata media overlay.
Adjust the label element width to 120px to be able to fit in the text: 'Comments'.
Adjust the font-weight of the comments & format value to 'normal'.
2018-02-19 21:25:22 +01:00
Borewit
0572f8a932 Update music-metadata dependency to 0.9.4. 2018-02-19 20:30:13 +01:00
Borewit
23d37d7da1 Merge branch 'master' into music-metadata 2018-02-19 20:22:22 +01:00
Feross Aboukhadijeh
474654fd16 standard 2018-02-18 15:49:46 -08:00
Feross Aboukhadijeh
c0d70e91b0 add github download count badge 2018-02-15 17:47:55 -08:00
Borewit
070d3ff242 Update music-metadata dependency to 0.9.2 2018-02-06 19:41:31 +01:00
Borewit
d5a62cb1a7 Add additional audio extensions: 'aiff', 'ape', 'mp2', 'oga', 'opus', 'wma' 2018-02-06 14:17:59 +01:00
Borewit
ba1f82faa0 Use direct file access, if the individual file has completed downloading.
Will allow slightly faster reading of music-metadata.
2018-02-05 20:29:17 +01:00
Borewit
0cce110c52 Fix bug #1320 by update music-metadata.
Bug was caused by an interoperability between then-read-stream and
readable-stream.
2018-02-05 20:20:53 +01:00
Borewit
bc91cde244 Merge branch 'master' into music-metadata 2018-02-05 10:19:17 +01:00
Borewit
30c9934694 Update version of dependency music-metadata to 0.9.0 2018-02-04 15:22:35 +01:00
Borewit
f56f3c62e9 Update version of dependency music-metadata to 0.8.8 2018-02-04 14:33:39 +01:00
Alberto Miranda
ac180a7cc5 1314: Restored package version. 2018-01-29 11:43:03 -03:00
Alberto Miranda
d1f3a16b7f 1315: Restored package version. 2018-01-29 11:42:23 -03:00
Alberto Miranda
e6c495417b 0.19.2 2018-01-29 09:24:38 -03:00
Alberto Miranda
c301608881 0.19.1 2018-01-29 09:24:32 -03:00
Alberto Miranda
7d2290a773 1315: Added initial state for highestPlaybackPriority. 2018-01-29 09:24:27 -03:00
Alberto Miranda
ba0d0b755e 0.19.1 2018-01-29 08:40:05 -03:00
Alberto Miranda
b5e9923469 1314: Fixed issue with undefined torrents to resume when app starts. 2018-01-29 08:40:01 -03:00
Feross Aboukhadijeh
b5bbd9b6a5 changelog 2018-01-26 16:52:39 +08:00
Feross Aboukhadijeh
5bf7618dde 0.19.0 2018-01-26 16:13:05 +08:00
Feross Aboukhadijeh
d02f6c7d18 Rewrite changelog for v0.19.0 2018-01-26 16:10:50 +08:00
Feross Aboukhadijeh
e69a18e548 authors 2018-01-26 15:56:59 +08:00
Feross Aboukhadijeh
991e2d2024 Update electron to 1.6.16 2018-01-26 15:56:59 +08:00
Feross Aboukhadijeh
9f31f30925 Mitigate protocol handler vulnerability 2018-01-26 15:56:59 +08:00
Feross Aboukhadijeh
340658c747 set version back to 0.18 -- will bump it during release 2018-01-26 15:56:09 +08:00
Alberto Miranda
5bbd9cfabb 0.19.0 2018-01-26 15:56:09 +08:00
Alberto Miranda
03a8b40ee9 Updated changelog. 2018-01-26 15:56:09 +08:00
Benjamin Tan
386bc950f4 Add back loading spinner for player page.
This appeared to have been removed accidentally when other parts of the CSS was refactored.
2018-01-22 20:05:33 +08:00
Benjamin Tan
d0cc157003 Merge pull request #1247 from auyer/master
"Error" text on missing Codec error changed to "Unable to Play"
2018-01-21 21:15:11 +08:00
Bilal Elmoussaoui
c990738c57 Fix the desktop file 2018-01-20 10:19:23 +01:00
Cezar Carneiro
01971e2a46 toggle fullscreen on end 2018-01-15 13:10:04 -05:00
SimplyAhmazing
08cb82e255 Update copyright a year later 2018-01-15 13:09:18 -05:00
Michael George Attard
962c563f2e Clean up handlers.js 2017-10-30 22:49:34 +01:00
Michael George Attard
bd6f2c2faf Clean up of main.js 2017-10-30 22:46:33 +01:00
Michael George Attard
ee8fe1e7ff Cleaned up external-player.js 2017-10-30 22:42:22 +01:00
Michael George Attard
424d171656 Used string template where it made sense. 2017-10-28 00:05:58 +02:00
Michael George Attard
7351133542 Used string template instead of concatenation. 2017-10-26 21:20:30 +02:00
Jon Koops
84deb403eb Update 'chromecasts' dependency to latest version
This should close:
- https://github.com/webtorrent/webtorrent-desktop/issues/1193
- https://github.com/webtorrent/webtorrent-desktop/issues/1202
2017-10-25 23:44:43 +02:00
Alberto Miranda
cae40b44e6 Merge pull request #1154 from webtorrent/watch-folder
Merging watch folder feature.
2017-10-16 22:47:03 -03:00
Auyer
ab0bbcea94 Error on missing Codec changed to "Unable to Play"
The error message was harsh, and new users might close the software
imediatly after it fails.
2017-10-03 11:14:44 -03:00
greenkeeper[bot]
06a7eff0ab docs(readme): add Greenkeeper badge 2017-09-29 01:59:08 +00:00
greenkeeper[bot]
341333c287 chore(package): update dependencies 2017-09-29 01:55:26 +00:00
Borewit
0f00985b75 Eliminated mime dependency 2017-09-20 09:14:51 +02:00
Borewit
c11a86d849 Fix max line length 2017-09-17 22:24:16 +02:00
Borewit
52e16c33b4 Fix lint error 2017-09-17 22:19:32 +02:00
Borewit
54e15644b6 Fix max line length 2017-09-17 22:16:01 +02:00
Borewit
6aa3a6c660 Fix lint errors 2017-09-17 22:14:07 +02:00
Borewit
83350b3b57 Add catalognumber in addition of the release label.
Updated to music-metadata in order to recognize MIME-type: audio/x-flac
2017-09-17 22:10:00 +02:00
Borewit
8868128d6e Add audio format information to play screen.
Extended album information with release lebel if available.
Make use of music-metadata module (musicmetadata appears to be no longer maintained)
2017-09-17 20:57:57 +02:00
Josip Janzic
f7ec537cd5 Add support for subtitles on Chromecast 2017-09-17 00:07:57 +02:00
Diego Rodríguez Baquero
a81156b363 Merge pull request #1233 from emilbayes/nojitter-time
Use tabular nums and fixed width for no jitter on time
2017-09-01 18:02:23 -05:00
Emil Bay
4b501ab90b Fixed width minutes 2017-08-28 19:41:30 +02:00
Emil Bay
90347c72e0 Use tabular nums and fixed width for no jitter on time 2017-08-28 19:07:24 +02:00
Cezar Carneiro
ab41d55e4b fix ETA and reorganize 2017-08-02 22:52:45 -03:00
Cezar Carneiro
739e7a50f7 improve infos 2017-08-02 00:03:52 -03:00
Cezar Carneiro
64ff5ebfd5 show percentage, size, speed and peers 2017-07-31 22:47:53 -03:00
Feross Aboukhadijeh
a51ab3573e Add a security policy 2017-07-17 12:45:13 -07:00
Egor Yurtaev
2aa2f0de19 Sort files alphanumerically #1185 2017-06-24 22:09:42 +06:00
Josip Janzic
96f5f855a3 Add missing key to torrent status element
Fixes #1116
2017-04-27 21:29:18 +02:00
Feross Aboukhadijeh
a82498ba16 gitter 2017-04-14 14:40:18 -07:00
Feross Aboukhadijeh
3b05b52e57 feross -> webtorrent 2017-04-14 13:48:39 -07:00
Alberto Miranda
b02d6f3cdb Preferences update
Renamed “Downloads” section to “Folders”; watch folder preference moved
under “Folders”; updated texts for watch folder preference.
2017-04-02 19:37:13 -03:00
Alberto Miranda
6e67cae494 Wait for .torrent files to finish downloading. 2017-03-26 11:04:05 -03:00
Alberto Miranda
23eef1a058 Removed unused module from folderWatcher. 2017-03-24 12:55:10 -03:00
Alberto Miranda
2ad3f6d6e2 Removed extra logging. 2017-03-24 12:54:28 -03:00
Alberto Miranda
996a81ddaa added folder-watcher-controller 2017-03-23 07:14:27 -03:00
Alberto Miranda
4a8ab6e1f6 fixed styling issue 2017-03-23 07:00:45 -03:00
Alberto Miranda
7f817241ed added start / stop events; starting / stopping folder watcher. 2017-03-22 23:21:07 -03:00
Alberto Miranda
a6c53d06d2 fixed styling issues 2017-03-22 22:27:03 -03:00
Alberto Miranda
a70c4d1bf2 Working on watch-folder
Added chokidar to watch for folder changes; added folder-watcher;
passing state to delayedInit on main;  added default values for new
preferences; added “Auto add torrents” preference with its checkbox and
path selector; TODO: start/stop watching on preference change, start
watching on init, add dialog when trying to enable preference without a
torrents folder.
2017-03-22 09:42:17 -03:00
Feross Aboukhadijeh
cdfb907c75 standard 2017-03-20 19:41:44 -07:00
Alberto Miranda
c2abb50e9e Merge pull request #840 from feross/playback-priority
Playback priority
2017-03-15 14:39:26 -03:00
Alberto Miranda
99f4fc96bf Fixed resuming when downloading finishes. 2017-03-14 19:52:29 -03:00
Alberto Miranda
043f81996e fixed styling issues; returning early in pauseActiveTorrents. 2017-03-14 19:50:49 -03:00
Alberto Miranda
213f2f124d Merge branch 'playback-priority' of https://github.com/feross/webtorrent-desktop into playback-priority 2017-03-14 19:44:45 -03:00
Alberto Miranda
96e7e83e20 Merge branch 'master' into playback-priority
# Conflicts:
#	src/renderer/controllers/playback-controller.js
#	src/renderer/controllers/torrent-list-controller.js
#	src/renderer/main.js
2017-03-14 19:43:57 -03:00
Feross Aboukhadijeh
e5fccd93a8 Tweak pinch-to-zoom timing
Toggle fullscreen immediately, then prevent future toggles for 1s.
2017-03-08 15:00:42 -08:00
Feross Aboukhadijeh
78e46f5576 Merge branch 'ebraminio-master' 2017-03-08 14:56:38 -08:00
Ebrahim Byagowi
69ce07fbf7 Toggle fullscreen on pinch to zoom 2017-03-08 23:13:05 +03:30
Feross Aboukhadijeh
827131e136 standard 2017-03-02 00:33:36 -08:00
Feross Aboukhadijeh
c764bf4884 fixes for standard v10 2017-03-01 23:08:29 -08:00
Feross Aboukhadijeh
972203d675 material-ui@0.17
Tested and everything seems to work alright with the new version.
2017-02-20 03:09:56 -08:00
Feross Aboukhadijeh
2c382e14b5 electron@1.6.0
This version of Electron is 3 versions of Chromium newer, so there are
lots of performance improvements.

There is one known issue with this update:
https://github.com/electron/electron/issues/8694

But I think we should merge this anyway so that collaborators can try
out the new Chromium engine before we do a release with it. We might
find other bugs.
2017-02-15 15:31:35 -08:00
Feross Aboukhadijeh
c298950d34 Add 'Start Speaking' and 'Stop Speaking' menu item (Mac)
Probably improves the accessibility situation a little bit: #439
2017-02-15 13:31:28 -08:00
Feross Aboukhadijeh
00f2e5ccd6 Treat .FLAC as playable audio
Fixes: https://github.com/feross/webtorrent-desktop/issues/1124

Note FLAC is not actually playable in Electron, by default. But this
will at least offer to open it in VLC or the user's preferred player.
2017-02-15 13:31:06 -08:00
Feross Aboukhadijeh
8d86cffabd standard 2017-02-08 16:07:25 -08:00
Feross Aboukhadijeh
95d6ec5fdd move release instructions to contributing.md 2017-02-04 18:00:50 -08:00
DC
9fbee6cfeb Replace release scripts with instructions
The release scripts don't really work, and there are a lot of manual steps that weren't documented. They are now.
2017-02-05 02:55:13 +01:00
Feross Aboukhadijeh
9892f88530 Fix 'About WebTorrent' menu location on Windows 2017-02-03 03:27:35 -08:00
Feross Aboukhadijeh
10ad990f97 update AUTHORS 2017-02-03 02:17:04 -08:00
Feross Aboukhadijeh
0ee7e80fd2 0.18.0 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
7d1520f858 disable background throttling in all windows 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
77ba258fbf move Transfers menu 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
06366529cc updated copyright year 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
d1c263f1b0 changelog for 0.18.0 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
d7a031a457 test: update test for new default trackers 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
b7201f424d test: update screenshot for new default trackers 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
89f2785244 remove unneeded package from depcheck 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
cffe416f50 package: fix deprecation warning 2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
d0eea34e9b lock down dev dependencies
Let's be very explicit about updating these. They are very critical to
the build process, so we should always read the changelogs / commit log
before upgrading.
2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
4e82718788 electron-osx-sign@0.4 2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
293ca60e72 electron@1.4.15 2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
af4a6a5960 buble@0.15 2017-02-03 02:01:54 -08:00
Nuno Campos
e01c0b6f43 Add textarea to list of html tags that skip document-wide onPaste 2017-02-02 15:17:10 -08:00
Feross Aboukhadijeh
efc984f1dd Include policy about copyrighted content in issues in issue template 2017-01-20 19:07:42 -08:00
Feross Aboukhadijeh
a046db40d2 Update react-tap-event-plugin 2.0.1
Fixes https://github.com/feross/webtorrent-desktop/issues/1086
2016-12-07 21:01:12 -08:00
Feross Aboukhadijeh
9068909b4d fixes for standard 2016-11-22 19:43:51 -03:00
Mathias Rasmussen
4bb2056bc9 Auto-focus modal 'OK' button to allow keypress 2016-10-24 14:43:19 -05:00
Karan Thakkar
ed0f05abc4 Pause All will now pause seeding torrents too 2016-10-24 13:33:38 -05:00
Karan Thakkar
0c44e10ca7 Move sound.play outisde the for loop 2016-10-24 13:33:38 -05:00
Karan Thakkar
d8c9014471 Add a new Transfers menu to allow pause all and resume all torrents 2016-10-24 13:33:38 -05:00
Feross Aboukhadijeh
7cf1d96a80 Fixes for PR #1050 2016-10-18 12:49:09 +01:00
Adam Gotlib
ffd9fbda57 Disallow saving torrent file until it is generated
Fixes #1031.
2016-10-18 12:46:33 +01:00
Adam Gotlib
d631ed1cc7 Store magnet URI once torrent has been parsed
Fixes #1035.
2016-10-15 17:00:03 +02:00
Adam Gotlib
d8904aaf6e Log successful attempts to open an external player 2016-10-13 23:31:34 -05:00
Adam Gotlib
de2db211cc Log play attempt separately for each file 2016-10-13 23:31:34 -05:00
Adam Gotlib
e8ab172d1b Remove unused 'timeout' field from telemetry 2016-10-13 23:31:34 -05:00
Alexey Romanov
32f96c03dd #1039: Formatting fixed.
Removed comma and added space after download speed.
2016-10-13 21:24:17 -07:00
Feross Aboukhadijeh
5158606740 Remove lodash.merge require() hack
See:
https://github.com/callemall/material-ui/pull/4380#issuecomment-25089455
2

My PR to material-ui to replace the full `lodash` with just the needed
functions was merged. So we can remove this hack.
2016-10-12 17:53:52 -05:00
Feross Aboukhadijeh
0de80165ed material-ui@0.16 2016-10-12 17:53:52 -05:00
Feross Aboukhadijeh
dcaa99d2bf es6-error@4 2016-10-12 17:53:52 -05:00
Feross Aboukhadijeh
1a1a4cd5d0 electron 1.4.3
Changelog: https://github.com/electron/electron/releases/tag/v1.4.3
2016-10-12 17:53:52 -05:00
Feross Aboukhadijeh
4cbad1fccd 0.17.2 2016-10-10 22:44:08 -07:00
Feross Aboukhadijeh
f818564dd1 package.json: Add {"private": true}
To prevent accidental publishing to npm.
2016-10-10 22:41:17 -07:00
Feross Aboukhadijeh
8be690a26e v0.17.2 changelog 2016-10-10 22:37:24 -07:00
Feross Aboukhadijeh
8081d42477 Attempt to fix "TypeError: Cannot read property 'startPiece' of undefined"
It wasn't clear what was causing this error, and I couldn't reproduce
it.

This PR attempts to resolve the issue by just guarding against the
exception.
2016-10-10 22:31:33 -07:00
Feross Aboukhadijeh
b0b6069fe2 remove 'pug' from nodemon command (we don't use pug) 2016-10-06 00:37:11 -07:00
Feross Aboukhadijeh
994aed9af7 Fix "Cannot read property 'files' of null"
This PR fixes one of our number 2 top error (142 error reports today
alone):

Processes: webtorrent window, platforms: darwin linux win32, versions:
pre-0.12 0.14.0 0.17.0 0.17.1
TypeError: Cannot read property 'files' of null
    at getAudioMetadata (.../build/renderer/webtorrent.js:328:21)
    at EventEmitter.<anonymous> (.../build/renderer/webtorrent.js:84:74)
    at emitThree (events.js:116:13)
    at EventEmitter.emit (events.js:194:7)

This error is reproducible if you start webtorrent for the first time
and click the WIRED CD torrent. This causes the webtorrent process to
get a  'wt-get-audio-metadata' before 'wt-start-torrenting'.

You can reproduce it 100% of the time if you force the race condition
to show itself by slowing down the sending of the 'wt-start-torrenting'
event.

(This same error was showing for an unrelated reason in the past: #891)
2016-10-05 03:00:52 -07:00
Feross Aboukhadijeh
852fc86cbd Remove unecessary return statement 2016-10-05 03:00:52 -07:00
Feross Aboukhadijeh
8801a87a58 Throttle browser-window 'move' and 'resize' events
Fixes: https://github.com/feross/webtorrent-desktop/issues/1011
2016-10-05 03:00:42 -07:00
Feross Aboukhadijeh
1e10f0083c Windows: Fix impossible to delete Wired CS torrent 2016-10-05 03:00:24 -07:00
Feross Aboukhadijeh
bb40f0f11a Update Mac integration test for Sierra 10.12
The font changed slightly on the next version of Mac OS. Let's update
the screenshots to match, since I already updated. (@dcposch - you'll
need to update too if you want the integration tests to pass on your
machine)
2016-10-04 01:53:49 -07:00
Feross Aboukhadijeh
78d7243a08 package: Explicitly specify architectures to build for
Electron now offers a "armv7l" build on Linux. Because we were
specifying "all", we were generating an armv7l binary which was being
included in "WebTorrent-vX.X.X-linux.zip" along with the ia32 build,
which explains why it was 90MB+ in the last release.
2016-10-03 04:40:42 -07:00
Feross Aboukhadijeh
09724dddd9 0.17.1 2016-10-03 04:02:21 -07:00
Feross Aboukhadijeh
59286ff3cb style: prefix all test commands with "test-"
It's a style I follow in all my packages...

- test
- test-node
- test-browser
- ...
2016-10-03 02:59:50 -07:00
Feross Aboukhadijeh
3f79c90868 Make Electron a devDependency
I moved it from devDependencies to dependencies when we added the app
to npm. But now that that's gone, let's move it back.

Functionally, this causes no difference since electron-packager
automatically excludes `electron` and all devDependencies from the
packaged app.
2016-10-03 02:59:50 -07:00
Feross Aboukhadijeh
33c48d4dfb cp-file@4
Only change is dropped Node 0.10 and 0.12 support. Nice change because
it means we load 3 fewer dependencies.
2016-10-03 02:38:03 -07:00
Feross Aboukhadijeh
48fbcd7303 v0.17.1 changelog 2016-10-03 02:28:25 -07:00
Feross Aboukhadijeh
cdb7b6eb44 Style fixes to PR #995 based on feedback 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
90b72523b7 perf: Only require('./user-tasks') on windows 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
a1fd30f4f5 cache mui theme after it is generated 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
fcae064dbb perf: ~40ms improvement: Lazy load controllers and page components 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
5815d8efe7 Fix first run telemetry bug 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
0be8a73621 perf: 140ms improvement: Hook into require() to modify how certain modules load 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
76e1d7777c Prevent exception when quitting and delayedInit() gets called 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
e10a84b294 Refactor: telemetry sending
- Fix bug where approxNumTorrents and other stats were not refreshed
when getting sent on 12 hour interval
- Lazy require modules
- Move setInterval into renderer/main.js
- Remove low-level https usage, use simple-get
2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
5ff2d893b9 perf: 90ms improvement: Defer more code in renderer, load state earlier
By deferring more code in the renderer and loading state earlier, we
improve startup time by another 90ms!

Before: 507 unique requires (1270-1280ms)
After: 506 unique requires  (1180-1190ms)
2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
02f5dbb63f perf: 60ms improvement: Replace fs-extra with mkdirp/rimraf/cp-file
In Electron apps, the cost of large modules is very real.

fs-extra is very convenient, but removing it caused 50 fewer unique
files to be required(), resultin in 60ms faster startup!

Before: 557 unique requires (1330-1340ms)
After: 507 unique requires (1270-1280ms)
2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
d4cfc32c8d re-order scripts 2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
d5820063a1 Perf: lazy-load more require() calls in main process
Went from 36 unique require calls, to 31 calls after this commit.
2016-10-03 01:24:53 -07:00
Feross Aboukhadijeh
21de048738 Remove unneeded console.logs 2016-10-03 01:24:53 -07:00
Benjamin Tan
06cdac8121 Remove weird outline from “Create Torrent” button.
The outline was caused by the wrong class being applied to the button.

Closes #989.
2016-10-02 21:35:26 -07:00
Mathias Rasmussen
f1aa1bdd59 go back to list on path missing error 2016-10-02 19:00:28 -07:00
Feross Aboukhadijeh
44f621b4de external player: Only run special .app login on darwin 2016-10-02 19:00:00 -07:00
Feross Aboukhadijeh
fd1a1f0f7e On paste: Do not handle multiple newline separated torrent ids
If the user accidentally pastes something that's not a torrent id, then
they get one "Invalid torrent Id" error for each line of the text.

Sure, this removes a "feature", but it's a pretty surprising one. When
I added it, I was being too clever, IMO.

The trim code can be removed, because that's handled in
controllers.torrentList.addTorrent().
2016-10-02 18:58:44 -07:00
Feross Aboukhadijeh
a38b5220ac Trim extra spaces off pasted magnet links
Before this change, using the "Open Torrent Address" dialog to paste a
magnet link would fail with leading or trailing spaces.

Pasting on the torrent list page has always trimmed. So this PR just
makes it consistent.
2016-10-02 18:58:44 -07:00
Feross Aboukhadijeh
428a07101a style: fix inconsistent naming
We don't use "ID" anywhere else in the codebase.
2016-10-02 18:58:44 -07:00
Feross Aboukhadijeh
f1ef9daa8f Update Electron to 1.4.2
Changelog: https://github.com/electron/electron/releases/tag/v1.4.2
2016-10-02 18:58:03 -07:00
Feross Aboukhadijeh
46d5c9c9e9 sound: Reduce volume even further 2016-10-01 19:36:06 -07:00
Feross Aboukhadijeh
ecd877551e sound: remove preloading
Sound playing is basically instant -- I was over-engineering when I
added this.
2016-10-01 19:36:06 -07:00
Feross Aboukhadijeh
76e071e965 Style: Use let/const instead of var 2016-10-01 00:50:04 +02:00
Mathias Rasmussen
5623c1024e delay external player if server is not ready 2016-09-30 01:14:32 -07:00
Feross Aboukhadijeh
1a20155f66 Disable Windows delta updates
See inline comment for explanation.
2016-09-28 09:48:51 +02:00
Benjamin Tan
9a17313a0c Remove unnecessary files after removal of npm package. 2016-09-28 09:28:31 +02:00
Feross Aboukhadijeh
b3ec61ddd8 Make Portable App also a Silent App
Fixes two portable app bugs, to make the app fully "silent", not just
"portable". This means that not only are all data files stored in the
"Portable Settings" folder, but the app should leave no trace on the
host system.

- Disable Electron's single instance mode so no lock file is created in
"%APPDATA%\Roaming\WebTorrent".

- Put Electron crash files, and other electron files into the "Portable
Settings\Temp" folder instead of "%APPDATA%\Temp".
2016-09-28 09:26:26 +02:00
Benjamin Tan
7dcddf90e9 Remove .nodemonignore file.
Adding configuration files for every tool used will clutter up the
repository, especially for a configuration as simple as this.
2016-09-28 09:03:46 +02:00
Feross Aboukhadijeh
a94fdcae61 Fix "Download path missing" for Portable App first run
Create "Portable Settings/Downloads" folder to prevent "Download path
missing" warning.
2016-09-28 04:22:05 +02:00
Feross Aboukhadijeh
f9bb83815f Switch fs.statSync to fs.accessSync, which is faster 2016-09-27 22:59:00 +02:00
Feross Aboukhadijeh
dccaf16a02 Merge pull request #973 from feross/feross/style
Pixel values should use numbers, not strings
2016-09-27 20:58:53 +02:00
Feross Aboukhadijeh
f4ee831319 Pixel values should use numbers, not strings
React converts to string and adds 'px' automatically
2016-09-27 11:56:12 -07:00
Feross Aboukhadijeh
5bcf1c6379 Merge pull request #972 from feross/f/controlst
Don't show pointer cursor on torrent list checkbox
2016-09-27 20:54:53 +02:00
Feross Aboukhadijeh
d2eb87e821 Merge pull request #974 from feross/feross/portable
Fix Windows Portable App mode
2016-09-27 20:47:07 +02:00
Feross Aboukhadijeh
be08925eb4 Fix Windows Portable App mode
Fixes: #971

This is a perfect example of putting too many statements into a
try-catch block. My bad. I was trying to keep the code simple, but it
bit us here.

This happens because we were using IS_PRODUCTION, but the order of the
consts at the top are:

const IS_PORTABLE = isPortable()
const IS_PRODUCTION = isProduction()

So we're inside of isPortable() and referring to IS_PRODUCTION before
it's defined. This should have thrown an exception, since const does
not allow use-before-define, but we're transforming to ES5 with Babel.

Also, standard could have caught this, but we can't enable the
use-before-define rule until this bug is fixed:
https://github.com/feross/standard/issues/636

Basically, a perfect storm.
2016-09-26 22:54:59 -07:00
Feross Aboukhadijeh
5698c22e75 Don't show pointer cursor on torrent list checkbox
Native apps don't have the "hand" cursor
2016-09-26 17:28:45 -07:00
DC
2114532f62 Integration tests: fix on Windows 2016-09-25 19:58:11 -07:00
DC
815ba00a6b Integration tests: fix on Mac 2016-09-25 19:43:09 -07:00
DC
bcd9f046fb No more npm publish 2016-09-25 18:24:38 -07:00
DC
bf3b416662 changelog 2016-09-25 18:23:00 -07:00
DC
736832c4e3 authors 2016-09-25 18:23:00 -07:00
DC
f08c0995a2 0.17.0 2016-09-25 18:23:00 -07:00
DC
de8a4d1160 Design: smaller progress bar 2016-09-25 18:23:00 -07:00
DC
739c1f705e Design: torrent list expand and collapse handle 2016-09-25 18:22:53 -07:00
DC
a32889291f Design: don't show 'Paused' on new torrents 2016-09-25 17:57:15 -07:00
Noam Okman
1e7e4cafd4 Use the progress bar from Material UI (#967) 2016-09-25 15:42:07 -07:00
Feross Aboukhadijeh
d5bed6c50a Add more peer ID entropy (#960) 2016-09-23 12:14:50 -07:00
Dan Flettre
8f827c9aae avoid saving window bounds when on player (#964) 2016-09-23 12:09:06 -07:00
DC
24fe033e2f Design: bring back the old startup sound
Talked about it w @feross, the new one sounds kind of like an error or warning
2016-09-23 03:44:52 -07:00
DC
85d04f931b Design: update integration test screenshots 2016-09-23 03:42:13 -07:00
DC
41b111c8a8 Design: address PR comments 2016-09-23 03:33:16 -07:00
DC
72db60bb12 Player: handle case where torrent-to-HTTP server hasn't started yet 2016-09-23 02:59:57 -07:00
DC
076eb009b9 Fix bug where playback rate could go negative 2016-09-23 02:59:57 -07:00
DC
789bd0ce82 Don't hide header when moused over player controls 2016-09-23 02:59:52 -07:00
DC
5155fca0e4 Design: off white primary color 2016-09-23 02:59:48 -07:00
DC
3b6819f894 Unmount audio/video tag when exiting player 2016-09-23 02:59:48 -07:00
DC
0dd1683298 Remove play button spinner, go to player page immediately 2016-09-23 02:59:48 -07:00
DC
2788d7433b Design: torrent list 2016-09-23 02:59:48 -07:00
DC
504a2419f6 Design: remove extra CSS 2016-09-23 02:59:48 -07:00
DC
75e5316ba1 Design: no gradients, nicer default colors
Before, the gradient transparent black overlay made text hard to read in some cases. Torrents without a poster image showed up in blue-gray and didn't look good.
2016-09-23 02:59:47 -07:00
DC
3be018521a Torrent list redesign 2016-09-23 02:59:47 -07:00
Feross Aboukhadijeh
6d375d5b5b Wait for 'ready-to-show' event before showing window (#959)
This gets rid of the light gray to dark gray background color change on
the main window at startup. Makes the window show slightly later, but
it's gray for less time. Doesn't affect overall startup time. Feels
less jank IMO.

From the Electron docs:

> While loading the page, the 'ready-to-show' event will be emitted
when renderer process has done drawing for the first time, showing
window after this event will have no visual flash.
2016-09-23 02:59:22 -07:00
Feross Aboukhadijeh
5de39bd7e5 Merge pull request #957 from feross/f/perf
startup perf: Reduce require() calls
2016-09-23 04:38:12 +02:00
Feross Aboukhadijeh
a08d576851 startup perf: Reduce require() calls
Every require() that we do before the users sees UI reduces startup
time.

I used the following code (added to index.js) to log every require()
call in the main process:

```js
var Module = require('module')
var required = {}
Module.prototype.require = function (orig) {
  return function (id) {
    if (!required[id]) {
      required[id] = true
      console.log(`${id}   (from ${this.filename})`)
    }
    return orig.apply(this, arguments)
  }
}(Module.prototype.require)
```

From this I was able to learn that lots of modules were being required
that aren't actually used until later.

I also sent this related PR to eliminate another few require()s:
https://github.com/LinusU/node-application-config/pull/4

This increases startup time by 50ms.

We'll probably realize much bigger gains by following this same
procedure for the renderer process.
2016-09-22 16:33:26 -07:00
Feross Aboukhadijeh
b8bdf65514 Use subtler UI sounds (#945)
* Use subtler UI sounds

Fixes #858

* Second round of volume tweaks
2016-09-22 14:42:43 -07:00
Feross Aboukhadijeh
1c0c3d07ff Merge pull request #949 from feross/f/state
State cleanup, rename, & tweaks
2016-09-22 23:41:01 +02:00
Feross Aboukhadijeh
832980eb9a Update Electron to 1.4.1 (#955)
Changelog: https://github.com/electron/electron/releases/tag/v1.4.1

(Should fix the Spectron console issue that @dcposch reported.)
2016-09-22 14:39:32 -07:00
Feross Aboukhadijeh
7c158e9f2c Rename events to be consistent
- Make State.save() always throttle calls -- since that's the common
case.

- Immediate saves are now the exception, with State.saveImmediate().

- The function is called State.save(), so the dispatch event should be
'stateSave'.
2016-09-22 14:25:57 -07:00
Feross Aboukhadijeh
a98d22ed72 code style 2016-09-22 14:20:58 -07:00
Feross Aboukhadijeh
63b7d34e29 Merge pull request #954 from omrilitov/master
use arch to determine OS_SYSARCH
2016-09-22 23:07:20 +02:00
Omri Litov
67ae6061aa use arch to determine OS_SYSARCH 2016-09-22 23:29:02 +03:00
Feross Aboukhadijeh
a8a861260e main: Start loading state before app is ready (#952)
As mentioned in
https://github.com/feross/webtorrent-desktop/pull/827#discussion_r799219
59

We should load the state outside the app.on('ready') handler so there's
a chance it's ready by the time 'ready' fires.

This improves startup time by roughly 50ms on my Macbook 12".
2016-09-22 03:00:40 -07:00
Feross Aboukhadijeh
fc879d5801 Update electron-packager to v8 (#946)
* electron-packager@8

* package: updates for electron-packager 8
2016-09-22 02:42:43 -07:00
DC
2d2645e642 Integration test: announce list windows screenshot 2016-09-21 23:58:42 -07:00
DC
8e66f641ce Integration test: default announce list changed 2016-09-21 23:58:21 -07:00
DC
82c49b5fc5 Integration test: yet another round of fixes 2016-09-21 23:58:21 -07:00
DC
b57ee73035 Integration tests: extra wait in test-audio 2016-09-21 23:58:21 -07:00
Feross Aboukhadijeh
ed8f493b8b Merge pull request #950 from feross/f/config
Make config static, not dynamic
2016-09-21 22:46:30 +02:00
Feross Aboukhadijeh
9e853027da make config static, not dynamic
I don't think we should dynamically generate the config object -- it
makes it harder to figure out what's going on.
2016-09-21 13:08:32 -07:00
Feross Aboukhadijeh
1e05487acd state: Use dispatch instead of direct call 2016-09-21 11:48:23 -07:00
Feross Aboukhadijeh
167da9dfd5 Double wait time until quit
On my modern Macbook 12" I've run into "Saving state took too long.
Quitting.". We have users with spinning disk drives, so let's be a bit
more generous.
2016-09-21 11:46:41 -07:00
Feross Aboukhadijeh
46e138a376 state: Use debounce to throttle saves 2016-09-21 11:46:00 -07:00
DC
853db922f1 Integration test: windows screenshot fuzzy diff 2016-09-20 23:53:00 -07:00
DC
7bf51b11ee Integration test: screenshot timing 2016-09-20 23:51:38 -07:00
DC
3a286ae978 Integration test: wait for next song 2016-09-20 23:51:38 -07:00
DC
82245f0b5c Integration tests: README 2016-09-20 23:51:37 -07:00
DC
802a898394 Integration tests: offline by default 2016-09-20 23:51:37 -07:00
DC
2200fffa1e Fix integration tests on Windows 2016-09-20 23:51:37 -07:00
DC
f4b2e78e72 Fix Delete Data on Windows. Fixes #935 2016-09-20 23:51:37 -07:00
DC
ad1162c7de Integration tests on Windows 2016-09-20 23:51:33 -07:00
DC
ed4daeb560 Integration test reliability 2016-09-20 23:49:45 -07:00
Feross Aboukhadijeh
927ae16e4f Merge pull request #941 from feross/dc/perf
Fix a sad, sad bug that resulted in 100+MB config.json
2016-09-21 08:18:48 +02:00
Feross Aboukhadijeh
917c89542b Merge pull request #944 from feross/buble
Replace babel with bublé
2016-09-21 08:18:27 +02:00
Feross Aboukhadijeh
39570bd4d7 Replace babel with bublé
Pros of bubel over babel:

- No configuration (a la standard)
- Runs twice as fast, for quicker development
- Converts everything to ES5 (which is likely to be faster than ES6,
untested)
- Easy to swap Babel back in -- low commitment

Cons:
- Less battle-tested than Babel, but recommended by React core
developer so probably not too bad
- No babel plugin support, but we're not using that right now anyway.
Can switch back to babel if we need that later

BEFORE:

$ time npm run build

> webtorrent-desktop@0.16.0 build /Users/feross/code/webtorrent-desktop
> babel --quiet src --out-dir build

npm run build  3.07s user 0.27s system 115% cpu 2.902 total

AFTER:

$ time npm run build

> webtorrent-desktop@0.16.0 build /Users/feross/code/webtorrent-desktop
> buble src --output build

npm run build  1.38s user 0.16s system 114% cpu 1.343 total
2016-09-20 15:21:17 -07:00
DC
f368dfad81 Fix a sad, sad bug that resulted in 100+MB config.json 2016-09-19 22:31:26 -07:00
DC
205e17cc83 Merge pull request #937 from feross/dynamic-window-min-height
make WINDOW_MIN_HEIGHT use existing values
2016-09-19 19:30:05 -07:00
Dan Flettre
4e052c8059 make WINDOW_MIN_HEIGHT use existing values 2016-09-19 16:48:12 -05:00
Dan Flettre
0afecd6063 Merge pull request #827 from feross/window-position
remember window position
2016-09-19 16:04:46 -05:00
DC
41511c5615 Integration test: use /tmp 2016-09-18 19:17:07 -07:00
Feross Aboukhadijeh
b9d39e3c64 changelog 2016-09-18 03:03:45 -07:00
Feross Aboukhadijeh
c1f482a950 Fix app DMG image 2016-09-18 02:58:39 -07:00
Feross Aboukhadijeh
e424031ad9 Windows build path fix 2016-09-18 02:58:36 -07:00
Feross Aboukhadijeh
f43dc2fc98 Fix windows path rename 2016-09-18 02:11:23 -07:00
Feross Aboukhadijeh
9cdc73edce 0.16.0 2016-09-18 01:56:59 -07:00
Feross Aboukhadijeh
3d254fa075 changelog 2016-09-18 01:41:01 -07:00
Feross Aboukhadijeh
ed1e43015e Merge pull request #931 from feross/detect-arch
Add 64-bit Windows build
2016-09-18 10:37:05 +02:00
Feross Aboukhadijeh
e6cbbd73f0 Fix silly typo 2016-09-18 01:35:20 -07:00
Feross Aboukhadijeh
67dff7b38c Add sanity check 2016-09-18 01:33:51 -07:00
Feross Aboukhadijeh
ced67176a3 add changelog 2016-09-18 01:29:14 -07:00
Feross Aboukhadijeh
00ac8afe64 About window: Show 32-bit vs. 64-bit status 2016-09-18 01:22:16 -07:00
Feross Aboukhadijeh
a6964c4495 Change file name inside RELEASES-ia32 to match renamed file 2016-09-18 01:07:45 -07:00
Dan Flettre
aedbc3c32f remember window position 2016-09-18 00:29:37 -05:00
DC
6541291e0d Integration test: address PR comments 2016-09-17 20:35:54 -07:00
DC
711d274398 Integration test: mock cast, remove loading bar
This lets us use exact screenshots with no transparency.
2016-09-17 20:35:53 -07:00
DC
6c5861b9fc Test screenshots: Create Torrent raised button 2016-09-17 20:35:53 -07:00
DC
f7ab27f9fd Integration test: audio 2016-09-17 20:35:53 -07:00
DC
e4e789cc5b Integration test: screenshot compare ignoring transparency 2016-09-17 20:35:52 -07:00
DC
09b525fe58 Integration test: simplify offline mode 2016-09-17 20:35:52 -07:00
DC
9dabfc1367 Integration test: offline mode 2016-09-17 20:35:52 -07:00
DC
bdb733352a Integration test: video playback 2016-09-17 20:35:52 -07:00
DC
75a4655a0f Integration test: create torrents 2016-09-17 20:35:52 -07:00
DC
051c1516a0 Integration test: add existing torrent 2016-09-17 20:35:51 -07:00
DC
62c5b78358 Integration test: update README 2016-09-17 20:35:51 -07:00
DC
290913d07a Integration test: delete torrent + data 2016-09-17 20:35:51 -07:00
Feross Aboukhadijeh
77534d650a Add 64-bit Windows build
Right now all Windows users are running a 32-bit app, even if their OS
is 64-bit.

Here's the plan to improve things:

1. We release a 64-bit installer, in addition to the 32-bit installer.

2. We auto-detect in the browser when a visitor is on a 32-bit vs.
64-bit OS and try to offer them the right installer. When in doubt, we
give them the 32-bit installer since that's safest.

3. The auto-updater will return the right binaries for the architecture
the user is on. This means that all our existing users who have 64-bit
OSs but are running the 32-bit app will get updated to the 64-bit app
on the next update. Also, 64-bit users who accidentally download the
32-bit installer will also get the 64-bit app on the next update.

---

Other notes:

- We don't generate 32-bit delta files. See reasoning inline.
- The package script now deletes extraneous Squirrel files (i.e.
*.nupkg delta files for older versions of the app) which should make
uploading the right files to GitHub easier. :)

The binary file naming works like this:

- Most users are on 64-bit systems, so they get nice clean file names
that don't specify an architecture (WebTorrentSetup-v1.0.0.exe). The
32-bit build files have the same naming but contain the string "-ia32"
at the end. In a few years, we will be able to just stop producing the
32-bit build files entirely.

- This means that the "WebTorrent-v0.15.0-linux-x64.zip" linux build
file is changing to "WebTorrent-v0.15.0-linux.zip" to match the Windows
naming convention. The .deb installer files must contain to
architecture in order to install correctly, so those do not change.

- Mac is 100% 64-bit, so it does not change.
2016-09-17 19:37:50 -07:00
Feross Aboukhadijeh
a4c715e3f6 Merge pull request #928 from feross/detect-arch
Detect system architecture; send in update/telemetry
2016-09-17 16:21:46 -07:00
Feross Aboukhadijeh
7415d3cee5 Detect system architecture; send in update/telemetry
Detect the actual operating system CPU architecture. This is different
than `process.arch` which returns the architecture the binary was
compiled for.

This is just good info to have in the telemetry, but we're also sending
it in the update check so that eventually we can upgrade Windows 32-bit
apps to 64-bit, like Slack does.

Context:
https://github.com/feross/webtorrent-desktop/issues/873#issuecomment-247
722023
2016-09-16 19:24:21 -07:00
Feross Aboukhadijeh
e1ba9c89fe Merge pull request #922 from feross/raised
Use raised button for inline button
2016-09-16 15:45:20 -07:00
Feross Aboukhadijeh
0fcbe7369a Merge pull request #925 from feross/capture-frame
Use capture-frame package
2016-09-16 15:43:20 -07:00
Feross Aboukhadijeh
c8087b5b63 Merge pull request #926 from feross/electron-1.4.0
Electron 1.4.0
2016-09-16 15:42:44 -07:00
Feross Aboukhadijeh
065faca8eb Electron 1.4.0 2016-09-16 10:25:03 -07:00
Feross Aboukhadijeh
bcd6a38a05 Use capture-frame package
See: https://github.com/feross/capture-frame
Capture video screenshot from a `<video>` tag (at the current time)

Changes from our version:

- Added tests in Chrome/Firefox browsers.
- Use built-in TypeError (which is meant for bad arguments) instead of
custom IllegalArgumentError.
2016-09-16 10:14:39 -07:00
DC
fa67f9b82b changelog 2016-09-16 01:11:12 -07:00
DC
39acd0bd47 authors 2016-09-16 01:02:16 -07:00
DC
c549fcfc27 0.15.0 2016-09-16 01:01:32 -07:00
DC
45e838d4c3 Telemetry: add torrent stats 2016-09-16 00:54:07 -07:00
DC
64f49e4d4f Auto launch: start minimized on MacOS 2016-09-13 19:49:47 -07:00
DC
61caa90901 Auto launch: don't open a terminal on MacOS 2016-09-13 19:49:47 -07:00
Noam Okman
3e85289318 Pref: start automatically on login 2016-09-13 19:49:47 -07:00
Feross Aboukhadijeh
a629f287f0 Use raised button for inline button 2016-09-13 08:16:38 -07:00
Mathias Rasmussen
3a4906079b External player clean up (#914)
* minor `addSubtitles` clean up

* external player clean up
2016-09-12 17:46:48 -07:00
Kai Curtis
3edf21f457 Update mouse moved time on header hover (#919)
Previously, moving the mouse into the player window from the sides or
bottom would bring up the HUD, but moving the mouse in from the top
would not. With this commit, moving the mouse in from the top of the
window will also bring up the HUD.

Fixes feross/webtorrent-desktop#241
2016-09-12 17:46:23 -07:00
DC
785c44cd2a Integration test: torrent list 2016-09-08 23:55:37 -07:00
DC
1ad8a5313b Integration test: save failed screenshot comparisons 2016-09-08 19:13:14 -07:00
DC
967e161288 Integration test: screenshots 2016-09-08 19:10:28 -07:00
DC
fe8c3b190c Integration test: tape + spectron hello world 2016-09-08 19:10:28 -07:00
Benjamin Tan
993e7d77ad Fix notification click not working. (#912)
This was changed incorrectly in 2a1e987.
2016-09-08 16:16:43 -07:00
DC
e0be052df4 Fix Open Torrent Address modal
Fixes a bug introduced in 0.14.0: cicking OK works, but hitting Enter doesn't do anything
2016-09-07 13:28:49 -07:00
Adam Gotlib
d331bae548 Move error definitions to errors.js (#898) 2016-09-07 13:21:59 -07:00
Adam Gotlib
d88229694a Disable playback controls while in external player (#909) 2016-09-07 13:13:50 -07:00
Adam Gotlib
8da5b955d6 Make git ignore npm-debug.log (#896) 2016-09-05 20:04:21 -07:00
DC
54882679c1 Dedupe cast.js status handlers, fix #889 2016-09-04 15:10:39 -07:00
DC
f2007be1b0 Fix selectFiles error, fixes #891 2016-09-04 14:56:17 -07:00
DC
7a757f9e05 More info in torrentFileModtimes, fix #892 2016-09-04 14:27:22 -07:00
DC
678f961110 Fix Heading.js 2016-09-04 11:09:08 -07:00
DC
f0464c44fd Changelog fixes 2016-09-04 10:31:01 -07:00
DC
b323ee24c4 Design: forward/back buttons, remove bad CSS 2016-09-04 09:51:50 -07:00
DC
38aad2ee9c authors 2016-09-03 18:49:49 -07:00
DC
b69ca93d20 0.14.0 2016-09-03 18:49:49 -07:00
DC
e21a039e70 changelog 2016-09-03 18:49:49 -07:00
Adam Gotlib
11f8e428a0 Restore playback state when reopening player (#877) 2016-09-03 15:25:19 -07:00
Mathias Rasmussen
704455c432 Rename App.js -> app.js (#882) 2016-09-03 13:24:09 -07:00
Noam Okman
c25bee755c make "npm run watch" work on windows (#878) 2016-09-03 20:13:11 +02:00
Dan Flettre
373d598c29 Merge pull request #880 from feross/dc/design
Torrent list design
2016-09-02 21:30:36 -05:00
DC
b8effffa96 Delete unused defaultPlayFileIndex 2016-09-02 14:13:30 -07:00
DC
ca6a7917ce Design: progress bar styling 2016-09-02 12:25:55 -07:00
DC
033bdf7908 Design: removed hover/select brightness
All those gradients and brighness filters toggling on and off were getting annoying
2016-09-02 12:08:57 -07:00
DC
6fe03aa325 Material UI: upgrade modals
Also clean up the Create Torrent page, delete some redundant CSS, prevent click-and-drag inside a TextField from moving the whole window, and make all label and input fonts  a consistent 14px size.
2016-09-02 02:30:37 -07:00
DC
b93f41f564 Telemetry: fix stacktrace redaction 2016-09-01 20:05:37 -07:00
DC
3f6cc97a02 Style: no more var 2016-09-01 19:52:37 -07:00
DC
0bda5358bd Style: extra linting 2016-09-01 19:41:50 -07:00
DC
e0af554caa Material UI: consistent JSX style 2016-09-01 19:38:21 -07:00
DC
b98f8476f5 Material UI: make file names consistent 2016-09-01 19:38:21 -07:00
Adam Gotlib
30732305ff Add playlists feature (#871)
* Open multi-file torrents as playlists

* Add `repeat` and `shuffle` options to the player

* Autoplay first file in torrent

* replaces `pickFileToPlay` feature
* when reopening player, restores the most recently viewed file

* Add playlist navigation buttons to Windows thumbar

* Remove `repeat` and `shuffle` options

This reverts commit 9284122461.

* Play files in order they appear in torrent

* Clean up playlists code
2016-09-01 19:18:48 -07:00
Adam Gotlib
14102ab3e6 Fix error on Windows caused by setBadge (#867) 2016-09-01 16:00:57 -07:00
Mathias Rasmussen
df16b14586 early telemetry (#870) 2016-09-01 15:58:02 -07:00
Mathias Rasmussen
9dcbc1b1f6 ensure that torrent file metadata is present (#869) 2016-09-01 15:54:37 -07:00
PurgingPanda
d806fd502f Added progress bar to the metadata on the overview (#844)
* Added progress bar to Torrent metadata on the overview.

* Added progress bar to Torrent metadata on the overview.

* Made progress bar smaller

As proposed by dcposh.
2016-09-01 14:43:28 -07:00
DC
db650caf18 changelog 2016-08-31 18:54:02 -07:00
DC
2b6c9ffcdb 0.13.1 2016-08-31 18:49:18 -07:00
DC
1d4b8ab67d Fix Create Torrent 2016-08-31 17:32:22 -07:00
DC
6404168bee changelog 2016-08-31 16:23:45 -07:00
DC
6613366cff authors 2016-08-31 16:09:49 -07:00
DC
74349129f4 0.13.0 2016-08-31 16:09:41 -07:00
DC
cdc2c1d718 Fix Create Torrent file list 2016-08-31 15:53:00 -07:00
DC
f8cc155650 Material UI: finish Create Torrent 2016-08-31 03:35:27 -07:00
DC
b6bdeab50b depcheck: ignore generated code 2016-08-31 00:58:00 -07:00
DC
f528f6033f Preferences: fix Make Default 2016-08-31 00:54:31 -07:00
DC
ef1bc13c38 use depcheck (#841)
* use depcheck to replace our own check-dependencies script

* ignore packges used in npm scripts
2016-08-30 20:42:45 -07:00
DC
2f54feac74 Telemetry: handle undefined values (#851) 2016-08-30 20:22:49 -07:00
Alberto Miranda
44a0f760de Resuming paused torrents when streaming torrent finishes downloading (wt-done). 2016-08-30 12:09:23 -03:00
Mathias Rasmussen
75d30baaa5 Telemetry: handle undefined values 2016-08-29 03:08:54 +02:00
Noam Okman
990fb57839 ignore packges used in npm scripts 2016-08-27 21:40:04 +03:00
Noam Okman
1883341ddb use depcheck 2016-08-27 21:40:02 +03:00
DC
5f7cece6d1 Fix npm run package
A require() had the wrong case, which apparently works for `npm start` build but fails in the packaged app
2016-08-27 09:05:58 -07:00
Alberto Miranda
1e5ac1df9c removed logging 2016-08-27 03:56:08 -03:00
Alberto Miranda
4cdc3085ff not pausing active torrents when playing an already downloaded torrent. 2016-08-27 03:55:23 -03:00
Alberto Miranda
e483263d70 removed logging 2016-08-27 03:36:28 -03:00
Alberto Miranda
bd41bd4db8 added highest playback priority feature; added highest playback priority checkbox on preferences. 2016-08-27 03:35:53 -03:00
Feross Aboukhadijeh
89e77d34f4 Merge pull request #838 from feross/wt-init-timeend
fix: add missing console.timeEnd
2016-08-26 20:18:03 +02:00
Nate Goldman
5eeb8fd6fc fix: add missing console.timeEnd 2016-08-26 09:43:40 -07:00
Feross Aboukhadijeh
808fca031a standard 2016-08-25 17:57:40 -07:00
Feross Aboukhadijeh
6b3c1e3802 Merge pull request #836 from feross/dc/fixes
Telemetry: log version in errors
2016-08-25 15:34:21 +02:00
DC
f488ef7597 Telemetry: fix error logging bugs, [object Object] and [object HTMLMediaElement] 2016-08-25 05:56:46 -07:00
DC
2c179c7465 Telemetry: log version in errors 2016-08-25 03:50:13 -07:00
Mathias Rasmussen
f1cf37200e decrease setProgress updates (#833) 2016-08-25 04:16:30 +02:00
Feross Aboukhadijeh
d2da6881d6 Merge pull request #829 from feross/f/hat
Remove 'hat' dependency
2016-08-25 02:57:32 +02:00
Feross Aboukhadijeh
9c25de58de Merge pull request #830 from feross/f/imageoptim
ImageOptim: losslessly compress all images
2016-08-25 02:57:02 +02:00
Feross Aboukhadijeh
f9aeb676b4 ImageOptim: losslessly compress all images 2016-08-24 03:47:47 -07:00
Feross Aboukhadijeh
396d769bc8 Remove 'hat' dependency 2016-08-24 00:40:33 -07:00
Feross Aboukhadijeh
83901eecba Fixes for PR #825
cc @mathiasvr
2016-08-23 17:25:59 -07:00
Feross Aboukhadijeh
019728cff5 Merge pull request #825 from feross/m/path-error-buttons
Don't render radial progress on path-missing error
2016-08-24 02:20:36 +02:00
Feross Aboukhadijeh
a96241d151 Merge pull request #824 from feross/m/open-partial-files
Don't open partially downloaded files in external app
2016-08-24 02:12:47 +02:00
Feross Aboukhadijeh
ebc9771be5 Merge pull request #823 from feross/m/external-controls
Don't render player controls when using external player
2016-08-24 02:08:58 +02:00
Feross Aboukhadijeh
0c75bac364 Merge pull request #822 from feross/m/playback-sound
Prevent notification sound during playback
2016-08-24 02:05:06 +02:00
Feross Aboukhadijeh
3f7e2c1e4a remove dev tools log 2016-08-23 16:24:41 -07:00
Feross Aboukhadijeh
10bf38bdf0 Merge pull request #826 from feross/readme
Readme improvements
2016-08-24 01:22:17 +02:00
Feross Aboukhadijeh
02508d7d9e Merge pull request #821 from feross/m/fix-poster-image
fix player poster image css bug
2016-08-24 01:21:53 +02:00
Feross Aboukhadijeh
5cb295f722 readme 2016-08-23 16:19:16 -07:00
Feross Aboukhadijeh
b08d273996 Fixes for PR #817
See
https://github.com/feross/webtorrent-desktop/pull/817#discussion-diff-75
968122
2016-08-23 16:14:22 -07:00
Feross Aboukhadijeh
1e27d1803a Merge pull request #817 from feross/menu-error
Fix error with menus.
2016-08-24 01:12:45 +02:00
Mathias Rasmussen
9bc018cc02 don't render radial progress on path error 2016-08-23 22:54:01 +02:00
Mathias Rasmussen
73cdfc6d45 don't open partially downloaded files 2016-08-23 22:23:43 +02:00
Mathias Rasmussen
1afd650997 Don't render player controls for external player 2016-08-23 20:57:31 +02:00
Mathias Rasmussen
9c8eabb46c Prevent notification sound during playback 2016-08-23 20:43:47 +02:00
Mathias Rasmussen
b39884e68f fix player poster image css bug 2016-08-23 20:21:10 +02:00
Benjamin Tan
451d457426 Fix error with menus. 2016-08-23 22:42:05 +08:00
Feross Aboukhadijeh
82853aa017 more progress 2016-08-23 04:21:58 -07:00
Feross Aboukhadijeh
157226f75b create torrent page progress 2016-08-23 03:51:05 -07:00
Feross Aboukhadijeh
509691a85a fixes for new path structure 2016-08-23 03:06:03 -07:00
Feross Aboukhadijeh
8b3aee7e2d move pages to renderer/pages/ 2016-08-23 03:06:03 -07:00
Feross Aboukhadijeh
4025e669eb misc changes 2016-08-23 03:06:03 -07:00
Feross Aboukhadijeh
1a01d7ed92 Use Material UI; improve Preferences Page
New principles for our UI:

- Components should use inline styles whenever possible
- Let's shrink the size of main.css to < 100 lines over time so it just
contains typography and basic styles
- Always require just the individual component that is needed from
Material UI so that the whole library doesn't get loaded (important for
startup perf)
2016-08-23 03:06:03 -07:00
Feross Aboukhadijeh
b4976d27f2 update material icons font 2016-08-23 03:05:33 -07:00
Feross Aboukhadijeh
173d8444d7 Preferences page rehaul: use React components, UI improvements 2016-08-23 03:05:33 -07:00
Feross Aboukhadijeh
aa150b76a5 less janky startup 2016-08-23 03:05:33 -07:00
Feross Aboukhadijeh
e2b5e28e07 add npm run watch command 2016-08-23 03:05:33 -07:00
Feross Aboukhadijeh
1ad07d9977 gitignore folders 2016-08-23 03:05:33 -07:00
Feross Aboukhadijeh
8ba4056894 move main.css to css/main.css 2016-08-23 03:04:58 -07:00
Feross Aboukhadijeh
9ad0316dff ensure Segoe is only used on Windows 2016-08-23 02:57:09 -07:00
Feross Aboukhadijeh
854aae7dc5 Merge pull request #813 from avamsi/patch-1
Add 'Segoe UI'
2016-08-23 11:55:24 +02:00
DC
5b021cd42e Audio: support .m4a 2016-08-23 02:39:45 -07:00
Vamsi Krishna Avula
33417d9b7e add 'Segoe UI' 2016-08-23 14:19:07 +05:30
Feross Aboukhadijeh
275184214a Merge pull request #811 from JaKXz/chore/electron
Switch from electron-prebuilt to electron
2016-08-23 10:35:16 +02:00
Jason Kurian
1f9adbd3cf Switch from electron-prebuilt to electron 2016-08-23 04:32:09 -04:00
DC
092c207dce changelog 2016-08-23 00:31:01 -07:00
DC
603c24faed authors 2016-08-23 00:30:56 -07:00
DC
f259b32cce 0.12.0 2016-08-23 00:07:01 -07:00
DC
eba9aa3e17 Telemetry: log app version 2016-08-22 23:59:52 -07:00
Feross Aboukhadijeh
905eb1611e Merge pull request #807 from feross/dc/fixes
Fix playback + download of default torrents
2016-08-23 06:32:58 +02:00
DC
6d4b8c3c26 Fix playback + download of default torrents
There was a terrible bug introduced in 0809e20a6e -- clicking play on any of the default torrents in a fresh install of the app would fail and result in a 'path missing' error.

This fixes the bug, and also adds a migration step to clean up resulting broken config files
2016-08-22 21:21:32 -07:00
Feross Aboukhadijeh
6a46609cca Merge pull request #804 from feross/dc/fixes
Bugfixes
2016-08-23 01:46:05 +02:00
DC
e872282221 Fix Delete Torrent + Data for newly added magnet links
Before, if you added a magnet link and then tried to delete the torrent plus data before the file list was loaded, it would fail and throw an uncaught error

Fixes #803
2016-08-22 00:58:23 -07:00
DC
24ac5af5b4 Fix jumpToTime
Fixes #801
2016-08-22 00:58:23 -07:00
DC
0ee92fb632 Telemetry: redact stacktraces 2016-08-22 00:54:19 -07:00
Feross Aboukhadijeh
7cbc12c6ff Merge pull request #795 from feross/small-fixes
A bunch of small fixes
2016-08-22 02:04:19 +02:00
Feross Aboukhadijeh
60c82c73cd Merge pull request #793 from feross/debian-system-install
Add system-wide menu item for debian and derivates
2016-08-22 02:03:29 +02:00
Feross Aboukhadijeh
78790e73c7 Merge pull request #788 from feross/content-bounds
Only use setContentBounds for player view
2016-08-22 01:56:14 +02:00
Feross Aboukhadijeh
bf464de16f remove extra console.error
This prevents all errors from being logged twice
2016-08-21 16:55:16 -07:00
Feross Aboukhadijeh
0589963eed code style 2016-08-21 16:54:59 -07:00
Feross Aboukhadijeh
b79971eea5 show video title in webtorrent app for all external players 2016-08-21 16:54:45 -07:00
Feross Aboukhadijeh
d1e557f054 Ignore stdout/stderr from spawned player
This prevents stalling in players like mpv/mplayer for some reason.

I think this could be because of the large number of updates that are
being written to stdout that's filling up a buffer and preventing
playback from continuing.
2016-08-21 16:54:27 -07:00
Feross Aboukhadijeh
93ddb8d638 launch VLC fixes
We can show video title on start now, since we're setting it correctly.

Also, escape the title since it could contain spaces.
2016-08-21 16:53:23 -07:00
Feross Aboukhadijeh
06fdd80845 Merge pull request #792 from feross/dc/telemetry
Telemtry: post at least once a day
2016-08-21 05:48:45 +02:00
grunjol
b0b26f8300 add system-wide launcher and icons for debian and derivates 2016-08-20 23:08:51 -03:00
DC
1db890f5e7 Telemtry: post at least once a day
This ensures that people who keep the app running in the background
for days at a time are still counted as active users.
2016-08-20 18:30:22 -07:00
Feross Aboukhadijeh
0f80f96023 Merge branch 'mathiasvr-external-player' 2016-08-20 01:20:37 -07:00
Feross Aboukhadijeh
2d3673ea33 Fixes to PR #682
- Rename 'playInVlc' preference to 'openExternalPlayer' since we
support more than just VLC now.
- Add default pref options to state.js
2016-08-20 01:19:50 -07:00
Feross Aboukhadijeh
c28260611e Merge pull request #787 from feross/redundant-powersaver
If a power saver block already exists, do nothing
2016-08-20 09:45:00 +02:00
Feross Aboukhadijeh
b5dd00007a Merge pull request #789 from feross/ignore-dot
Ignore '.' argument which is annoying in development
2016-08-20 09:44:52 +02:00
Feross Aboukhadijeh
ac39264f3d Ignore '.' argument which is annoying in development 2016-08-19 22:44:26 -07:00
Feross Aboukhadijeh
667a04a41d Merge branch 'external-player' of https://github.com/mathiasvr/webtorrent-desktop into mathiasvr-external-player
Fixed conflicts in the Preferences page, and added back passing the video title to VLC
2016-08-19 22:06:23 -07:00
Feross Aboukhadijeh
51a9b2ea9b Only use setContentBounds for player view
Fixes: https://github.com/feross/webtorrent-desktop/issues/786
2016-08-19 20:05:52 -07:00
Feross Aboukhadijeh
842ee5ca3c If a power saver block already exists, do nothing
Before this change, when opening the player, both 'onPlayerOpen' and
'onPlayerPlay' would fire, which enabled, disabled, and re-enabled the
power save blocker in quick succession.

Instead, we just want to activate it once.
2016-08-19 20:04:18 -07:00
Feross Aboukhadijeh
2cc67dbda7 AUTHORS 2016-08-19 16:24:47 -07:00
Mathias Rasmussen
6c68645b0f Custom external media player 2016-07-26 23:57:33 +02:00
199 changed files with 34511 additions and 4047 deletions

View File

@@ -1,9 +0,0 @@
**What version of WebTorrent Desktop?** (See the 'About WebTorrent' menu)
**What operating system and version?**
**What did you do?**
**What did you expect to happen?**
**What actually happened?**

20
.github/ISSUE_TEMPLATE/BUG_REPORT.md vendored Normal file
View File

@@ -0,0 +1,20 @@
---
name: "🐞 Bug report"
about: Report an issue with this software
title: ''
labels: ''
assignees: ''
---
<!-- DO NOT POST LINKS OR REFERENCES TO COPYRIGHTED CONTENT IN YOUR ISSUE. -->
**What version of WebTorrent Desktop are you using?**
**What operating system and version?**
**What happened?**
**What did you expect to happen?**
**Are you willing to submit a pull request to fix this bug?**

View File

@@ -0,0 +1,20 @@
---
name: "⭐️ Feature request"
about: Request a new feature to be added
title: ''
labels: ''
assignees: ''
---
<!-- DO NOT POST LINKS OR REFERENCES TO COPYRIGHTED CONTENT IN YOUR ISSUE. -->
**What version of WebTorrent Desktop are you using?**
**What operating system and version?**
**What problem do you want to solve?**
**What do you think is the correct solution to this problem?**
**Are you willing to submit a pull request to implement this change?**

21
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: ci
on: [push,pull_request]
jobs:
test:
name: Node ${{ matrix.node }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
node:
- '16'
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm run build --if-present
- run: npm test

69
.github/workflows/package.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: package
on:
workflow_dispatch:
jobs:
package_linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v3
with:
node-version: '16'
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm install
- run: npm run package -- linux
- uses: actions/upload-artifact@v3
with:
name: linux
path: |
dist/*.deb
dist/*.rpm
dist/*.zip
package_macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v3
with:
node-version: '16'
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm install
- run: npm run package -- darwin
- uses: actions/upload-artifact@v3
with:
name: macos
path: |
dist/*.dmg
dist/*.zip
package_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v3
with:
node-version: '16'
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm install
- run: npm run package -- win32
- uses: actions/upload-artifact@v3
with:
name: windows
path: |
dist/*.exe
dist/*.nupkg
dist/*.zip

23
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 12 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?'
stale-pr-message: 'Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?'
exempt-issue-labels: accepted,blocked,dependency,security,meta
exempt-pr-labels: accepted,blocked,bug,dependency,security,meta
stale-issue-label: 'stale'
stale-pr-label: 'stale'

12
.gitignore vendored
View File

@@ -1,3 +1,9 @@
node_modules
build
dist
node_modules/
build/
dist/
npm-debug.log*
.vscode/
# JetBrains IntelliJ IDEA project files
.idea
*.iml

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
package-lock=true

View File

@@ -1,4 +0,0 @@
language: node_js
node_js:
- 'node'
install: npm install standard

View File

@@ -10,7 +10,6 @@
- Romain Beaumont (romain.rom1@gmail.com)
- Dan Flettre (fletd01@yahoo.com)
- Liam Gray (liam.r.gray@gmail.com)
- grunjol (grunjol@users.noreply.github.com)
- Rémi Jouannet (remijouannet@users.noreply.github.com)
- Evan Miller (miller.evan815@gmail.com)
- Alex (alxmorais8@msn.com)
@@ -24,8 +23,61 @@
- Thomas Watson Steen (w@tson.dk)
- anonymlol (anonymlol7@gmail.com)
- Gediminas Petrikas (gedas18@gmail.com)
- Alberto Miranda (codealchemist@gmail.com)
- Adam Gotlib (gotlib.adam+dev@gmail.com)
- Rémi Jouannet (remijouannet@gmail.com)
- Andrea Tupini (tupini07@gmail.com)
- grunjol (grunjol@gmail.com)
- Jason Kurian (jasonk92@gmail.com)
- Vamsi Krishna Avula (vamsi_ism@outlook.com)
- Noam Okman (noamokman@gmail.com)
- PurgingPanda (t3ch0wn3r@gmail.com)
- Kai Curtis (morecode@kcurtis.com)
- Omri Litov (omrilitov@gmail.com)
- Alexey Romanov (romanalexey@gmail.com)
- Karan Thakkar (karanjthakkar@gmail.com)
- Nuno Campos (nuno.campos@me.com)
- Ebrahim Byagowi (ebrahim@gnu.org)
- Josip Janzic (josip@jjanzic.com)
- Egor Yurtaev (yurtaev.egor@gmail.com)
- Emil Bay (github@tixz.dk)
- Borewit (borewit@users.noreply.github.com)
- greenkeeper[bot] (greenkeeper[bot]@users.noreply.github.com)
- Auyer (rafa_auyer@icloud.com)
- Jon Koops (jonkoops@gmail.com)
- Michael George Attard (michaelgeorgeattard@gmail.com)
- SimplyAhmazing (ahmad19526@gmail.com)
- Cezar Carneiro (cezargcarneiro@gmail.com)
- Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
- Terry Hau (terryhau@gmail.com)
- Vítor Galvão (info@vitorgalvao.com)
- Borewit (Borewit@users.noreply.github.com)
- Diego Rodríguez (diegorbaquero@gmail.com)
- Dan Flettre (flettre@gmail.com)
- Sibiraj (dev.sibiraj@outlook.com)
- clujin (clujin@gmail.com)
- Sharon Grossman (sharong1337@gmail.com)
- Linus Unnebäck (linus@folkdatorn.se)
- Adrian Tombu (adrian@otso.fr)
- Lucas (5874806+RecoX@users.noreply.github.com)
- David Ernst (dsernst@users.noreply.github.com)
- David Ernst (git@dsernst.com)
- Jimmy Wärting (jimmy@warting.se)
- Recox (5874806+RecoX@users.noreply.github.com)
- greenkeeper[bot] (23040076+greenkeeper[bot]@users.noreply.github.com)
- hicom150 (hicom150@gmail.com)
- Дамјан Георгиевски (gdamjan@gmail.com)
- Jimmy Wärting (jimmy@warting.se)
- Julen Garcia Leunda (hicom150@gmail.com)
- Feross (feross@feross.org)
- Daniele Debernardi (drebrez@gmail.com)
- Chandan Chowdary Bhagam (chandandharana@gmail.com)
- Pieter Goetschalckx (3.14.e.ter@gmail.com)
- Carey Metcalfe (carey@cmetcalfe.ca)
- Ameet Kaustav (akaustav@users.noreply.github.com)
- gpatarin (gael.patarin@outlook.com)
- Gael Patarin (gael.patarin@outlook.com)
- Subin Siby (mail@subinsb.com)
- Hinara (hinara.turevel@gmail.com)
#### Generated by bin/update-authors.sh.

View File

@@ -1,21 +1,278 @@
# WebTorrent Desktop Version History
## v0.24.0 - 2020-08-28
### Added
- Support the `.m2ts` video container format ([hicom150](https://github.com/hicom150))
### Changed
- Update to Electron 10.1.0 [\#1864](https://github.com/webtorrent/webtorrent-desktop/pull/1864) ([feross](https://github.com/feross))
- Update the Windows installer loading image [\#1841](https://github.com/webtorrent/webtorrent-desktop/pull/1841) ([alxhotel](https://github.com/alxhotel))
### Fixed
- Fix music metadata not showing up [\#1847](https://github.com/webtorrent/webtorrent-desktop/pull/1847) ([Borewit](https://github.com/Borewit))
- Fix the "Play in VLC" functionality [\#1850](https://github.com/webtorrent/webtorrent-desktop/pull/1850) ([Hinara](https://github.com/Hinara))
- Prevent shortcuts from activating when user input elements are focused [\#1840](https://github.com/webtorrent/webtorrent-desktop/pull/1840) ([subins2000](https://github.com/subins2000))
## v0.23.0 - 2020-07-15
🔒 This release contains a critical security fix. Please update as soon as possible. [\#1837](https://github.com/webtorrent/webtorrent-desktop/issues/1837#issuecomment-729320901)
### Added
- Add macOS Notarization [\#1834](https://github.com/webtorrent/webtorrent-desktop/pull/1834) ([feross](https://github.com/feross))
### Changed
- Update to Electron 10 beta [\#1834](https://github.com/webtorrent/webtorrent-desktop/pull/1834) ([feross](https://github.com/feross))
## v0.22.0 - 2020-07-15
❤️✨ A new version of WebTorrent Desktop is out! ❤️✨
### Added
- Linux `.rpm` packages and `arm64` builds are now available! [\#1694](https://github.com/webtorrent/webtorrent-desktop/pull/1694) ([hicom150](https://github.com/hicom150))
- Add support for multiple audio tracks [\#1712](https://github.com/webtorrent/webtorrent-desktop/pull/1712) ([hicom150](https://github.com/hicom150))
- Improve codec unsupported detection [\#1711](https://github.com/webtorrent/webtorrent-desktop/pull/1711) ([hicom150](https://github.com/hicom150))
- Report when files are being verified [\#1717](https://github.com/webtorrent/webtorrent-desktop/pull/1717) ([pR0Ps](https://github.com/pR0Ps))
- Support additional audio files: MPEG-Layer-2, Musepack, Matroska audio, WavePack [\#1772](https://github.com/webtorrent/webtorrent-desktop/pull/1772)
### Changed
- Update to Electron 9 [\#1729](https://github.com/webtorrent/webtorrent-desktop/pull/1729) [\#1832](https://github.com/webtorrent/webtorrent-desktop/issues/1832)
- Update to music-metadata 4.8.0 [\#1719](https://github.com/webtorrent/webtorrent-desktop/pull/1719) ([Borewit](https://github.com/Borewit))
- Update Windows build documentation [\#1715](https://github.com/webtorrent/webtorrent-desktop/pull/1715) ([RecoX](https://github.com/RecoX))
- Remove unneeded dependencies ([feross](https://github.com/feross))
### Fixed
- Fix a few type errors [\#1720](https://github.com/webtorrent/webtorrent-desktop/pull/1720) ([mathiasvr](https://github.com/mathiasvr))
- Fix electron SUID sandbox error [\#1707](https://github.com/webtorrent/webtorrent-desktop/pull/1707) ([hicom150](https://github.com/hicom150))
- Fix percentage rounding error [\#1716](https://github.com/webtorrent/webtorrent-desktop/pull/1716) ([pR0Ps](https://github.com/pR0Ps))
- Fix path-selector in preferences page [\#1702](https://github.com/webtorrent/webtorrent-desktop/pull/1702) ([314eter](https://github.com/314eter))
- Fix path-selector in preferences page [\#1704](https://github.com/webtorrent/webtorrent-desktop/pull/1702) ([mathiasvr](https://github.com/mathiasvr))
- Fix: Increase height of 'About' window [\#1737](https://github.com/webtorrent/webtorrent-desktop/pull/1737) ([akaustav](https://github.com/akaustav))
- Fix "Save Torrent File As..." [\#1743](https://github.com/webtorrent/webtorrent-desktop/pull/1743) ([gpatarin](https://github.com/gpatarin))
## v0.21.0 - 2019-09-14
### Added
- Add YouTube style hotkeys [\#1579](https://github.com/webtorrent/webtorrent-desktop/pull/1579) ([dsernst](https://github.com/dsernst))
- Toggle sound notifications on/off [\#1536](https://github.com/webtorrent/webtorrent-desktop/pull/1536) ([adriantombu](https://github.com/adriantombu))
- Ability to play MPEG-4 Audio Book \(.m4b\) [\#1450](https://github.com/webtorrent/webtorrent-desktop/pull/1450) ([Borewit](https://github.com/Borewit))
- Add support for subtitles on Chromecast [\#1165](https://github.com/webtorrent/webtorrent-desktop/pull/1165) ([janza](https://github.com/janza))
### Changed
- Update to Electron 4 [\#1590](https://github.com/webtorrent/webtorrent-desktop/pull/1590) ([Borewit](https://github.com/Borewit))
- Remove '\(BETA\)' from app window title [\#1562](https://github.com/webtorrent/webtorrent-desktop/pull/1562) ([dsernst](https://github.com/dsernst))
- Update React (v16) and Material-UI (v0.20) [\#1483](https://github.com/webtorrent/webtorrent-desktop/pull/1483) ([mathiasvr](https://github.com/mathiasvr))
- Show audio track and disk number [\#1454](https://github.com/webtorrent/webtorrent-desktop/pull/1454) ([Borewit](https://github.com/Borewit))
- Asynchronous music metadata updates while streaming [\#1449](https://github.com/webtorrent/webtorrent-desktop/pull/1449) ([Borewit](https://github.com/Borewit))
- If torrent is not private, leave private flag unset [\#1411](https://github.com/webtorrent/webtorrent-desktop/pull/1411) ([feross](https://github.com/feross))
- Improve audio poster selection: [\#1368](https://github.com/webtorrent/webtorrent-desktop/pull/1368) ([Borewit](https://github.com/Borewit))
- Save preferences immediately when changed [\#1042](https://github.com/webtorrent/webtorrent-desktop/pull/1042) ([Flet](https://github.com/Flet))
### Fixed
- Ensure that the minutes field in playback indicator is zero-padded. [\#1506](https://github.com/webtorrent/webtorrent-desktop/pull/1506) ([bnjmnt4n](https://github.com/bnjmnt4n))
- Bug Fix: Empty Array Reduce [\#1494](https://github.com/webtorrent/webtorrent-desktop/pull/1494) ([clujin](https://github.com/clujin))
- Fix startup problems [\#1419](https://github.com/webtorrent/webtorrent-desktop/pull/1419) ([Borewit](https://github.com/Borewit))
- Add back loading spinner for player page. [\#1311](https://github.com/webtorrent/webtorrent-desktop/pull/1311) ([bnjmnt4n](https://github.com/bnjmnt4n))
- Fix Linux desktop file [\#1309](https://github.com/webtorrent/webtorrent-desktop/pull/1309) ([bilelmoussaoui](https://github.com/bilelmoussaoui))
## v0.20.0 - 2018-04-26
### Added
- Added support for additional audio extensions: 'aiff', 'ape', 'mp2', 'oga', 'opus', 'wma' (#1240)
### Changed
- Displaying filename while music metadata is being downloaded (#1361)
- Improved the poster selection for audio/music based torrents (#1334)
- Launch VLC player without the `--video-on-top` flag (#1286)
### Fixed
- Fix silently failing to open magnets links on Linux (#1367)
## v0.19.0 - 2018-01-26
### Added
- Added watch folder feature: Automatically add new torrent files added to a folder on disk (#1154)
- Added highest playback priority feature: pauses other active torrents when playback starts (#840)
- Add 'Start Speaking' and 'Stop Speaking' menu item (Mac) (#439)
- Add pinch-to-zoom gesture to enter/exit fullscreen (#1148)
### Changed
- [SECURITY] Mitigate Electron protocol handler issue (Windows)
- Moved project from Feross's GitHub account to the WebTorrent GitHub organization
- Updated to electron@1.6.16
- Updated to material-ui@0.17
- Treat .FLAC as playable audio (#1127)
### Fixed
- Fix time and duration so it doesn't bounce in the UI (#1233)
- Fix 'About WebTorrent' menu location on Windows (#1120)
## v0.18.0 - 2017-02-03
### Added
- Add a new "Transfers" menu for pausing or resuming all torrents (#1027)
### Changed
- Update Electron to 1.4.15
- Windows 32-bit: App can use 4GB of memory instead of just 2GB
- Fix "Portable App" writing crash reports to "%APPDATA%\Temp" (Windows)
- Updated WebTorrent engine to 0.98.5
- Fix issue where http web seeds would sometimes stall
- Don't send 'completed' event to tracker again if torrent is already complete
- Add more peer ID entropy
- Set user-agent header for tracker http requests
### Fixed
- Fix paste shortcut in tracker list on Create Torrent page (#1112)
- Auto-focus the 'OK' button in modal dialogs (#1058)
- Fix formatting issue in the speed stats on the Player page (#1039)
## v0.17.2 - 2016-10-10
### Fixed
- Windows: Fix impossible-to-delete "Wired CD" default torrent
- Throttle browser-window 'move' and 'resize' events
- Fix crash ("Cannot read property 'files' of null" error)
- Fix crash ("TypeError: Cannot read property 'startPiece' of undefined")
## v0.17.1 - 2016-10-03
### Changed
- Faster startup (improved by ~25%)
- Update Electron to 1.4.2
- Remove support for pasting multiple newline-separated magnet links
- Reduce UX sound volume
### Fixed
- Fix external player (VLC, etc.) opening before HTTP server was ready
- Windows (Portable App): Fix "Portable App" mode
- Write application support files to the "Portable Settings" folder
- Stop writing Electron "single instance" lock file to "%APPDATA%\Roaming\WebTorrent"
- Some temp data is still written to "%APPDATA%\Temp" (will be fixed in future version)
- Don't show pointer cursor on torrent list checkbox
- Trim extra whitespace from magnet links pasted into "Open Torrent Address" dialog
- Fix weird outline on 'Create Torrent' button
## v0.17.0 - 2016-09-23
### Added
- Remember window size and position
### Changed
- Torrent list redesign
- Quieter, more subtle sounds
- Got rid of the play button spinner, now goes to the player immediately
- Faster startup
### Fixed
- Fix bug where playback rate could go negative
- Don't hide header when moused over player controls
- Fix Delete Data File on Windows
- Fix a sad, sad bug that resulted in 100+ MB config files
- Fix app DMG background image
## v0.16.0 - 2016-09-18
### Added
- **Windows 64-bit support!** (#931)
- Existing 32-bit users will update to 64-bit automatically in next release
- 64-bit reduces likelihood of out-of-memory errors by increasing the address space
### Fixed
- Mac: Fix background image on .DMG
## v0.15.0 - 2016-09-16
### Added
- Option to start automatically on login
- Add integration tests
- Add more detailed telemetry to diagnose "buffer allocation failed"
### Changed
- Disable playback controls while in external player (#909)
### Fixed
- Fix several uncaught errors (#889, #891, #892)
- Update to the latest webtorrent.js, fixing some more uncaught errors
- Clicking on the "torrent finished" notification works again (#912)
## v0.14.0 - 2016-09-03
### Added
- Autoplay through all files in a torrent (#871)
- Torrents now have a progress bar (#844)
### Changed
- Modals now use Material UI
- Torrent list style improvements
### Fixed
- Fix App.js crash in Linux (#882)
- Fix error on Windows caused by `setBadge` (#867)
- Don't crash when restarting after adding a magnet link (#869)
- Restore playback state when reopening player (#877)
## v0.13.1 - 2016-08-31
### Fixed
- Fixed the Create Torrent page
## v0.13.0 - 2016-08-31
### Added
- Support .m4a audio
- Better telemetry: log error versions, report more types of errors
### Changed
- New look - Material UI. Rewrote Create Torrent and Preferences pages.
### Fixed
- Fixed telemetry [object Object] and [object HTMLMediaElement] bugs
- Don't render player controls when playing externally, eg in VLC
- Don't play notification sounds during media playback
## v0.12.0 - 2016-08-23
### Added
- Custom external media player
- Linux: add system-wide launcher and icons for Debian, including Ubuntu
### Changed
- Telemetry improvements: redact stacktraces, log app version
### Fixed
- Fix playback and download of default torrents ("missing path" error) (#804)
- Fix Delete Torrent + Data for newly added magnet links
- Fix jumpToTime error (#804)
## v0.11.0 - 2016-08-19
### Added
- New Preference to "Set WebTorrent as default handler for torrents and magnet links" (#771)
- New Preference to "Always play in VLC" (#674)
- Check for missing default download path and torrent folders on start up (#776)
### Changed
- Do not automatically set WebTorrent as the default handler for torrents (#771)
- Torrents can only be created from the home screen (#770)
- Update Electron to 1.3.3 (#772)
### Fixed
- Allow modifying the default tracker list on the Create Torrent page (#775)
- Prevent opening multiple stacked Preference windows or Create Torrent windows (#770)
- Windows: Player window auto-resize does not match video aspect ratio (#565)
@@ -24,13 +281,11 @@
## v0.10.0 - 2016-08-05
### Added
- Drag-and-drop magnet links (selected text) is now supported (#284)
- Windows: Add "User Tasks" shortcuts to app icon in Start Menu (#114)
- Linux: Show badge count for completed torrent downloads
### Changed
- Change WebTorrent Desktop peer ID prefix to 'WD' to distinguish from WebTorrent in the browser, 'WW' (#688)
- Switch UI to React to improve UI rendering speed (#729)
- The primary bottleneck was actually `hyperx`, not `virtual-dom`.
@@ -47,7 +302,6 @@
- Location history abstraction released independently as [`location-history`](https://www.npmjs.com/package/location-history)
### Fixed
- When streaming to VLC, set VLC window title to torrent file name (#746)
- Fix "Cannot read property 'numPiecesPresent' of undefined" exception (#695)
- Fix rare case where config file could not be completely written (#733)
@@ -55,35 +309,29 @@
## v0.9.0 - 2016-07-20
### Added
- Save selected subtitles
- Ask for confirmation before deleting torrents
- Support Debian Jessie
### Changed
- Only send telemetry in production
- Clean up the code. Split main.js, refactor lots of things
### Fixed
- Fix state.playing.jumpToTime behavior
- Remove torrent file and poster image when deleting a torrent
## v0.8.1 - 2016-06-24
### Added
- New URI handler: stream-magnet
### Fixed
- DLNA crashing bug
## v0.8.0 - 2016-06-23
### Added
- Cast menu: choose which Chromecast, Airplay, or DLNA device you want to use
- Telemetry: send basic data, plus stats on how often the play button works
- Make posters from jpeg files, not just jpg
@@ -91,17 +339,14 @@
- Windows thumbnail bar with a play/pause button
### Changed
- Nicer modal styles
### Fixed
- Windows tray icon now stays in the right state
## v0.7.2 - 2016-06-02
### Fixed
- Fix exception that affects users upgrading from v0.5.1 or older
- Ensure `state.saved.prefs` configuration exists
- Fix window title on "About WebTorrent" window
@@ -109,23 +354,19 @@
## v0.7.1 - 2016-06-02
### Changed
- Change "Step Forward" keyboard shortcut to `Alt+Left` (Windows)
- Change "Step Backward" keyboard shortcut to to `Alt+Right` (Windows)
### Fixed
- First time startup bug -- invalid torrent/poster paths
## v0.7.0 - 2016-06-02
### Added
- Improved AirPlay support -- using the new [`airplayer`](https://www.npmjs.com/package/airplayer) package
- Remember volume setting in player, for as long as the app is open
### Changed
- Add (+) button now also accepts non .torrent files and creates a torrent from
those files
- Show prompt text in title bar for open dialogs (OS X)
@@ -135,7 +376,6 @@
- Fix crash reporter not working (Windows)
### Fixed
- Re-enable WebRTC (web peers)! (OS X, Windows)
- Windows support was disabled in v0.6.1 to work around a bug in Electron
- OS X support was disabled in v0.4.0 to work around a 100% CPU bug
@@ -146,7 +386,6 @@
- Fix torrent loading message UI misalignment
### Known issues
- When upgrading to WebTorrent Desktop v0.7.0, some torrent metadata (file list,
selected files, whether torrent is streamable) will be cleared. Just start the
torrent to re-populate the metadata.
@@ -154,7 +393,6 @@
## v0.6.1 - 2016-05-26
### Fixed
- Disable WebRTC to work around Electron crash (Windows)
- Will be re-enabled in the next version of WebTorrent, which will be based on
the next version of Electron, where the bug is fixed.
@@ -166,7 +404,6 @@
## v0.6.0 - 2016-05-24
### Added
- Added Preferences page to set Download folder
- Save video position, resume playback from saved position
- Add additional video player keyboard shortcuts (#275)
@@ -176,7 +413,6 @@
- Add announcement feature
### Changed
- Nicer player UI
- Reduce startup jank, improve startup time (#568)
- Cleanup unsupported codec detection (#569, #570)
@@ -184,7 +420,6 @@
- Improve subtitle positioning (#551)
### Fixed
- Fix Uncaught TypeError: Cannot read property 'update' of undefined (#567)
- Fix bugs in LocationHistory
- When player is active, and magnet link is pasted, go back to list
@@ -195,23 +430,19 @@
## v0.5.1 - 2016-05-18
### Fixed
- Fix auto-updater (OS X, Windows).
## v0.5.0 - 2016-05-17
### Added
- Select/deselect individual files to torrent.
- Automatically include subtitle files (.srt, .vtt) from torrent in the subtitles menu.
- "Add Subtitle File..." menu item.
### Changed
- When manually adding subtitle track(s), always switch to the new track.
### Fixed
- Magnet links throw exception on app launch. (OS X)
- Multi-file torrents would not seed in-place, were copied to Downloads folder.
- Missing 'About WebTorrent' menu item. (Windows)
@@ -220,7 +451,6 @@
## v0.4.0 - 2016-05-13
### Added
- Better Windows support!
- Windows 32-bit build.
- Windows Portable App build.
@@ -243,7 +473,6 @@
- New default torrent on first launch: The WIRED CD.
### Changed
- Improve app startup time by 40%.
- UI tweaks: Reduce font size, reduce torrent list item height.
- Add Playback menu for playback-related functionality.
@@ -254,7 +483,6 @@
- Remove "Add Fake Airplay/Chromecast" menu items.
### Fixed
- Disable WebRTC to fix 100% CPU usage/crashes caused by Chromium issue. This is
temporary. (OS X)
- When fullscreen, make controls use the full window. (OS X)
@@ -279,24 +507,20 @@ to this release!
## v0.3.3 - 2016-04-07
### Fixed
- App icon was incorrect (OS X)
## v0.3.2 - 2016-04-07
### Added
- Register WebTorrent as default handler for magnet links (OS X)
### Changed
- Faster startup time (50ms)
- Update Electron to 0.37.5
- Remove the white flash when loading pages and resizing the window
- Fix crash when sending IPC messages
### Fixed
- Fix installation bugs with .deb file (Linux)
- Pause audio reliably when closing the window
- Enforce minimimum window size when resizing player (for audio-only .mov files, which are 0x0)
@@ -304,17 +528,14 @@ to this release!
## v0.3.1 - 2016-04-06
### Added
- Add crash reporter to torrent engine process
### Fixed
- Fix cast screen background: cover, don't tile
## v0.3.0 - 2016-04-06
### Added
- **Ubuntu/Debian support!** (.deb installer)
- **DLNA streaming support**
- Add "File > Quit" menu item (Linux)
@@ -322,14 +543,12 @@ to this release!
- Crash reporting
### Changed
- On startup, do not re-verify files when timestamps are unchanged
- Moved torrent engine to an independent process, for better UI performance
- Removed media queries (UI resizing based on window width)
- Improved Chromecast icon, when connected
### Fixed
- "Download Complete" notification shows consistently
- Create new torrents and seed them without copying to temporary folder
- Clicking the "Download Complete" notification will always activate app
@@ -348,7 +567,6 @@ Thanks to @dcposch, @grunjol, and @feross for contributing to this release.
## v0.2.0 - 2016-03-29
### Added
- Minimise to tray (Windows, Linux)
- Show spinner and download speed when player is stalled waiting for data
- Highlight window on drag-and-drop
@@ -357,12 +575,10 @@ Thanks to @dcposch, @grunjol, and @feross for contributing to this release.
Linux users need to download new versions manually.
### Changed
- Renamed WebTorrent.app to WebTorrent Desktop
- Add Cosmos Laundromat as a default torrent
### Fixed
- Only capture media keys when player is active
- Update WebTorrent to 0.88.1 for performance improvements
- When seeding, do not proactively connect to new peers
@@ -422,7 +638,7 @@ Windows, and Linux. For now, we're only releasing binaries for OS X.
WebTorrent Desktop is in ALPHA and under very active development  expect lots more polish in
the coming weeks! If you know JavaScript and want to help us out, there's
[lots to do](https://github.com/feross/webtorrent-desktop/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)!
[lots to do](https://github.com/webtorrent/webtorrent-desktop/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)!
### Features

View File

@@ -1,95 +0,0 @@
# Contributing Guidelines
Contributions welcome!
**Before spending lots of time on something, ask for feedback on your idea first!**
Please search issues and pull requests before adding something new to avoid duplicating
efforts and conversations.
This project welcomes non-code contributions, too! The following types of contributions
are welcome:
- **Ideas**: participate in an issue thread or start your own to have your voice heard.
- **Writing**: contribute your expertise in an area by helping expand the included docs.
- **Copy editing**: fix typos, clarify language, and improve the quality of the docs.
- **Formatting**: help keep docs easy to read with consistent formatting.
## Code Style
[![standard][standard-image]][standard-url]
This repository uses [`standard`][standard-url] to maintain code style and consistency,
and to avoid style arguments. `npm test` runs `standard` automatically, so you don't have
to!
[standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg
[standard-url]: https://github.com/feross/standard
## Project Governance
Individuals making significant and valuable contributions are given commit-access to the
project to contribute as they see fit. This project is more like an open wiki than a
standard guarded open source project.
### Rules
There are a few basic ground-rules for contributors:
1. **No `--force` pushes** or modifying the Git history in any way.
2. **Non-master branches** should be used for ongoing work.
3. **Significant modifications** like API changes should be subject to a **pull request**
to solicit feedback from other contributors.
4. **Pull requests** are *encouraged* for all contributions to solicit feedback, but left to
the discretion of the contributor.
### Releases
Declaring formal releases remains the prerogative of the project maintainer.
### Changes to this arrangement
This is an experiment and feedback is welcome! This document may also be subject to pull-
requests or changes by contributors where you believe you have something valuable to add
or change.
## Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
- (a) The contribution was created in whole or in part by me and I have the right to
submit it under the open source license indicated in the file; or
- (b) The contribution is based upon previous work that, to the best of my knowledge, is
covered under an appropriate open source license and I have the right under that license
to submit that work with modifications, whether created in whole or in part by me, under
the same open source license (unless I am permitted to submit under a different
license), as indicated in the file; or
- (c) The contribution was provided directly to me by some other person who certified
(a), (b) or (c) and I have not modified it.
- (d) I understand and agree that this project and the contribution are public and that a
record of the contribution (including all personal information I submit with it,
including my sign-off) is maintained indefinitely and may be redistributed consistent
with this project or the open source license(s) involved.
## Smoke Tests
Before a release, check that the following basic use cases work correctly:
1. Click "Play" to stream a built-in torrent (e.g. Sintel)
- Ensure that seeking to undownloaded region works and plays immediately.
- Ensure that sintel.mp4 gets downloaded to `~/Downloads`.
2. Check that the auto-updater works
- Open the console and check for the line "No update available" to indicate
3. Add a new .torrent file via drag-and-drop.
- Ensure that it gets added to the list and starts downloading
4. Remove a torrent from the client
- Ensure that the file is removed from `~/Downloads`
5. Create and seed a new a torrent via drag-and-drop.
- Ensure that the torrent gets created and seeding begins.

138
README.md
View File

@@ -1,45 +1,110 @@
<h1 align="center">
<br>
<a href="https://webtorrent.io"><img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200"></a>
<a href="https://webtorrent.io">
<img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200">
</a>
<br>
WebTorrent Desktop
<br>
<br>
</h1>
<h4 align="center">The streaming torrent client. For Mac, Windows, and Linux.</h4>
<h4 align="center">The streaming torrent app. For Mac, Windows, and Linux.</h4>
<p align="center">
<a href="https://gitter.im/feross/webtorrent"><img src="https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg" alt="Gitter"></a>
<a href="https://travis-ci.org/feross/webtorrent-desktop"><img src="https://img.shields.io/travis/feross/webtorrent-desktop/master.svg" alt="Travis"></a>
<a href="https://github.com/feross/webtorrent-desktop/releases"><img src="https://img.shields.io/github/release/feross/webtorrent-desktop.svg" alt="Release"></a>
<a href="https://discord.gg/cnXkm4Z"><img src="https://img.shields.io/discord/612575111718895616" alt="discord"></a>
<a href="https://github.com/webtorrent/webtorrent-desktop/actions/workflows/ci.yml"><img src="https://github.com/webtorrent/webtorrent-desktop/actions/workflows/ci.yml/badge.svg" alt="GitHub CI action"></a>
<a href="https://github.com/webtorrent/webtorrent-desktop/releases"><img src="https://img.shields.io/github/release/webtorrent/webtorrent-desktop.svg" alt="github release version"></a>
<a href="https://github.com/webtorrent/webtorrent-desktop/releases"><img src="https://img.shields.io/github/downloads/webtorrent/webtorrent-desktop/total.svg" alt="github release downloads"></a>
<a href="https://standardjs.com"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="Standard - JavaScript Style Guide"></a>
</p>
## Install
**WebTorrent Desktop** is still under very active development. You can download the latest version from the [releases](https://github.com/feross/webtorrent-desktop/releases) page.
### Recommended Install
## Screenshot
Download the latest version of WebTorrent Desktop from
[the official website](https://webtorrent.io/desktop/):
### [✨ Download WebTorrent Desktop ✨](https://webtorrent.io/desktop/)
### Advanced Install
- Download specific installer files from the [GitHub releases](https://github.com/webtorrent/webtorrent-desktop/releases) page.
- Use [Homebrew-Cask](https://github.com/caskroom/homebrew-cask) to install from the command line:
```
$ brew install --cask webtorrent
```
- Try the (unstable) development version by cloning the Git repository. See the
["How to Contribute"](#how-to-contribute) instructions.
## Screenshots
<p align="center">
<img src="https://webtorrent.io/img/screenshot-player3.png" alt="screenshot" align="center">
<img src="https://webtorrent.io/img/screenshot-main.png" width="612" height="749" alt="screenshot" align="center">
</p>
## How to Contribute
### Install dependencies
### Get the code
```
$ git clone https://github.com/webtorrent/webtorrent-desktop.git
$ cd webtorrent-desktop
$ npm install
```
### Run app
### Run the app
```
$ npm start
```
### Package app
### Watch the code
Restart the app automatically every time code changes. Useful during development.
```
$ npm run watch
```
### Run linters
```
$ npm test
```
### Run integration tests
```
$ npm run test-integration
```
The integration tests use Spectron and Tape. They click through the app, taking screenshots and
comparing each one to a reference. Why screenshots?
* Ad-hoc checking makes the tests a lot more work to write
* Even diffing the whole HTML is not as thorough as screenshot diffing. For example, it wouldn't
catch an bug where hitting ESC from a video doesn't correctly restore window size.
* Chrome's own integration tests use screenshot diffing iirc
* Small UI changes will break a few tests, but the fix is as easy as deleting the offending
screenshots and running the tests, which will recreate them with the new look.
* The resulting Github PR will then show, pixel by pixel, the exact UI changes that were made! See
https://github.com/blog/817-behold-image-view-modes
For MacOS, you'll need a Retina screen for the integration tests to pass. Your screen should have
the same resolution as a 2018 MacBook Pro 13".
For Windows, you'll need Windows 10 with a 1366x768 screen.
When running integration tests, keep the mouse on the edge of the screen and don't touch the mouse
or keyboard while the tests are running.
### Package the app
Builds app binaries for Mac, Linux, and Windows.
@@ -50,7 +115,7 @@ $ npm run package
To build for one platform:
```
$ npm run package -- [platform]
$ npm run package -- [platform] [options]
```
Where `[platform]` is `darwin`, `linux`, `win32`, or `all` (default).
@@ -60,36 +125,65 @@ The following optional arguments are available:
- `--sign` - Sign the application (Mac, Windows)
- `--package=[type]` - Package single output type.
- `deb` - Debian package
- `rpm` - RedHat package
- `zip` - Linux zip file
- `dmg` - Mac disk image
- `exe` - Windows installer
- `portable` - Windows portable app
- `all` - All platforms (default)
Note: Even with the `--package` option, the auto-update files (.nupkg for Windows, *-darwin.zip for Mac) will always be produced.
Note: Even with the `--package` option, the auto-update files (.nupkg for Windows,
-darwin.zip for Mac) will always be produced.
#### Windows build notes
To package the Windows app from non-Windows platforms, [Wine](https://www.winehq.org/) needs
to be installed.
The Windows app can be packaged from **any** platform.
On Mac, first install [XQuartz](http://www.xquartz.org/), then run:
Note: Windows code signing only works from **Windows**, for now.
Note: To package the Windows app from non-Windows platforms,
[Wine](https://www.winehq.org/) and [Mono](https://www.mono-project.com/) need
to be installed. For example on Mac, first install
[XQuartz](http://www.xquartz.org/), then run:
```
brew install wine
$ brew install wine mono
```
(Requires the [Homebrew](http://brew.sh/) package manager.)
#### Mac build notes
The Mac app can only be packaged from **macOS**.
#### Linux build notes
The Linux app can be packaged from **any** platform.
If packaging from Mac, install system dependencies with Homebrew by running:
```
npm run install-system-deps
```
#### Recommended readings to start working in the app
Electron (Framework to make native apps for Windows, OSX and Linux in Javascript):
https://electronjs.org/docs/tutorial/quick-start
React.js (Framework to work with Frontend UI):
https://reactjs.org/docs/getting-started.html
Material UI (React components that implement Google's Material Design.):
https://material-ui.com/getting-started/installation
### Privacy
WebTorrent Desktop collects some basic usage stats to help us make the app better. For example, we track how well the play button works. How often does it succeed? Time out? Show a missing codec error?
WebTorrent Desktop collects some basic usage stats to help us make the app better.
For example, we track how well the play button works. How often does it succeed?
Time out? Show a missing codec error?
The app never sends personally identifying information, nor does it track which swarms you join.
### Code Style
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
The app never sends any personally identifying information, nor does it track which
torrents you add.
## License

109
RELEASE_PROCESS.md Normal file
View File

@@ -0,0 +1,109 @@
## Release Process
### 1. Create a new version
- Update `AUTHORS`
```
npm run update-authors
```
Commit if necessary. The commit message should be "authors".
- Write the changelog
You can use `git log --oneline <last version tag>..HEAD` to get a list of changes.
Summarize them concisely in `CHANGELOG.md`. The commit message should be "changelog".
- Update the version
```
npm version [major|minor|patch]
```
This creates both a commit and a git tag.
- Make a PR
Once the PR is reviewed, merge it:
```
git push origin <branch-name>:master
```
This makes it so that the commit hash on master matches the commit hash of the version tag.
Finally, run:
```
git push --tags
```
### 2. Create the release binaries
- On a Mac:
```
npm run package -- darwin --sign
```
Move the `.zip` and `.dmg` file somewhere because the next step wipes the `dist/` folder away.
```
npm run package -- linux --sign
```
- On Windows, or in a Windows VM:
```
npm run package -- win32 --sign
```
- Then, upload the release binaries to Github:
```
npm run gh-release
```
Follow the URL to a newly created Github release page. Manually upload the binaries from
`webtorrent-desktop/dist/`. Open the previous release in another tab, and make sure that you
are uploading the same set of files, no more, no less.
### 3. Test it
**This is the most important part.**
- Manually download the binaries for each platform from Github.
**Do not use your locally built binaries.** Modern OSs treat executables differently if they've
been downloaded, even though the files are byte for byte identical. This ensures that the
codesigning worked and is valid.
- Smoke test WebTorrent Desktop on each platform. Before a release, check that the following basic use cases work correctly:
1. Click "Play" to stream a built-in torrent (e.g. Sintel)
- Ensure that seeking to undownloaded region works and plays immediately.
- Ensure that sintel.mp4 gets downloaded to `~/Downloads`.
2. Check that the auto-updater works
- Open the console and check for the line "No update available" to indicate that the auto-updater is working. (If the auto updater does not run, users will successfully auto update to this new version, and then be stuck there forever.)
3. Add a new .torrent file via drag-and-drop.
- Ensure that it gets added to the list and starts downloading.
4. Remove a torrent from the client
- Ensure that the file is removed from `~/Downloads`
5. Create and seed a new a torrent via drag-and-drop.
- Ensure that the torrent gets created and seeding begins.
### 4. Ship it
- Update the website
Create a pull request in [webtorrent.io](https://github.com/webtorrent/webtorrent.io). Update
`config.js`, updating the desktop app version.
Once this PR is merged and Feross redeploys the WebTorrent website,
hundreds of thousands of users around the world will start auto updating. **Merge with care.**

View File

@@ -1,105 +0,0 @@
#!/usr/bin/env node
var fs = require('fs')
var cp = require('child_process')
// We can't use `builtin-modules` here since our TravisCI
// setup expects this file to run with no dependencies
var BUILT_IN_NODE_MODULES = [
'assert',
'buffer',
'child_process',
'cluster',
'console',
'constants',
'crypto',
'dgram',
'dns',
'domain',
'events',
'fs',
'http',
'https',
'module',
'net',
'os',
'path',
'process',
'punycode',
'querystring',
'readline',
'repl',
'stream',
'string_decoder',
'timers',
'tls',
'tty',
'url',
'util',
'v8',
'vm',
'zlib'
]
var BUILT_IN_ELECTRON_MODULES = [ 'electron' ]
var BUILT_IN_DEPS = [].concat(BUILT_IN_NODE_MODULES, BUILT_IN_ELECTRON_MODULES)
var EXECUTABLE_DEPS = [
'gh-release',
'standard',
'babel-cli',
'babel-plugin-syntax-jsx',
'babel-plugin-transform-react-jsx'
]
main()
// Scans codebase for missing or unused dependencies. Exits with code 0 on success.
function main () {
if (process.platform === 'win32') {
console.error('Sorry, check-deps only works on Mac and Linux')
return
}
var usedDeps = findUsedDeps()
var packageDeps = findPackageDeps()
var missingDeps = usedDeps.filter(
(dep) => !includes(packageDeps, dep) && !includes(BUILT_IN_DEPS, dep)
)
var unusedDeps = packageDeps.filter(
(dep) => !includes(usedDeps, dep) && !includes(EXECUTABLE_DEPS, dep)
)
if (missingDeps.length > 0) {
console.error('Missing package dependencies: ' + missingDeps)
}
if (unusedDeps.length > 0) {
console.error('Unused package dependencies: ' + unusedDeps)
}
if (missingDeps.length + unusedDeps.length > 0) {
process.exitCode = 1
}
}
// Finds all dependencies specified in `package.json`
function findPackageDeps () {
var pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
var deps = Object.keys(pkg.dependencies)
var devDeps = Object.keys(pkg.devDependencies)
var optionalDeps = Object.keys(pkg.optionalDependencies)
return [].concat(deps, devDeps, optionalDeps)
}
// Finds all dependencies that used with `require()`
function findUsedDeps () {
var stdout = cp.execSync('./bin/list-deps.sh')
return stdout.toString().trim().split('\n')
}
function includes (arr, elem) {
return arr.indexOf(elem) >= 0
}

View File

@@ -5,13 +5,13 @@
* Useful for developers.
*/
var fs = require('fs')
var os = require('os')
var path = require('path')
var rimraf = require('rimraf')
const fs = require('fs')
const os = require('os')
const path = require('path')
const rimraf = require('rimraf')
var config = require('../src/config')
var handlers = require('../src/main/handlers')
const config = require('../src/config')
const handlers = require('../src/main/handlers')
// First, remove generated files
rimraf.sync('build/')
@@ -21,11 +21,11 @@ rimraf.sync('dist/')
rimraf.sync(config.CONFIG_PATH)
// Remove any temporary files
var tmpPath
let tmpPath
try {
tmpPath = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent')
} catch (err) {
tmpPath = path.join(os.tmpDir(), 'webtorrent')
tmpPath = path.join(os.tmpdir(), 'webtorrent')
}
rimraf.sync(tmpPath)

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env node
var electron = require('electron-prebuilt')
var cp = require('child_process')
var path = require('path')
var child = cp.spawn(electron, [path.join(__dirname, '..')], {stdio: 'inherit'})
child.on('close', function (code) {
process.exitCode = code
})

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>

View File

@@ -1,10 +0,0 @@
#!/bin/sh
# This is a truly heinous hack, but it works pretty nicely.
# Find all modules we're requiring---even conditional requires.
grep "require('" src/ bin/ -R |
grep '.js:' |
sed "s/.*require('\([^'\/]*\).*/\1/" |
grep -v '^\.' |
sort |
uniq

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
var config = require('../src/config')
var open = require('open')
const { CONFIG_PATH } = require('../src/config')
const open = require('open')
open(config.CONFIG_PATH)
open(CONFIG_PATH)

View File

@@ -1,28 +1,28 @@
#!/usr/bin/env node
/**
* Builds app binaries for Mac, Linux, and Windows.
* Builds app binaries for Mac, Windows, and Linux.
*/
var cp = require('child_process')
var electronPackager = require('electron-packager')
var fs = require('fs')
var minimist = require('minimist')
var mkdirp = require('mkdirp')
var os = require('os')
var path = require('path')
var rimraf = require('rimraf')
var series = require('run-series')
var zip = require('cross-zip')
const cp = require('child_process')
const electronPackager = require('electron-packager')
const fs = require('fs')
const minimist = require('minimist')
const os = require('os')
const path = require('path')
const rimraf = require('rimraf')
const series = require('run-series')
const zip = require('cross-zip')
var config = require('../src/config')
var pkg = require('../package.json')
const config = require('../src/config')
const pkg = require('../package.json')
var BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
var BUILD_PATH = path.join(config.ROOT_PATH, 'build')
var DIST_PATH = path.join(config.ROOT_PATH, 'dist')
const BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
const BUILD_PATH = path.join(config.ROOT_PATH, 'build')
const DIST_PATH = path.join(config.ROOT_PATH, 'dist')
const NODE_MODULES_PATH = path.join(config.ROOT_PATH, 'node_modules')
var argv = minimist(process.argv.slice(2), {
const argv = minimist(process.argv.slice(2), {
boolean: [
'sign'
],
@@ -36,14 +36,19 @@ var argv = minimist(process.argv.slice(2), {
})
function build () {
console.log('Installing node_modules...')
rimraf.sync(NODE_MODULES_PATH)
cp.execSync('npm ci', { stdio: 'inherit' })
console.log('Nuking dist/ and build/...')
rimraf.sync(DIST_PATH)
rimraf.sync(BUILD_PATH)
console.log('Babel: Building JSX...')
console.log('Build: Transpiling to ES5...')
cp.execSync('npm run build', { NODE_ENV: 'production', stdio: 'inherit' })
console.log('Babel: Built JSX.')
console.log('Build: Transpiled to ES5.')
var platform = argv._[0]
const platform = argv._[0]
if (platform === 'darwin') {
buildDarwin(printDone)
} else if (platform === 'win32') {
@@ -61,35 +66,35 @@ function build () {
}
}
var all = {
const all = {
// The human-readable copyright line for the app. Maps to the `LegalCopyright` metadata
// property on Windows, and `NSHumanReadableCopyright` on Mac.
'app-copyright': config.APP_COPYRIGHT,
appCopyright: config.APP_COPYRIGHT,
// The release version of the application. Maps to the `ProductVersion` metadata
// property on Windows, and `CFBundleShortVersionString` on Mac.
'app-version': pkg.version,
appVersion: pkg.version,
// Package the application's source code into an archive, using Electron's archive
// format. Mitigates issues around long path names on Windows and slightly speeds up
// require().
asar: true,
// A glob expression, that unpacks the files with matching names to the
// "app.asar.unpacked" directory.
'asar-unpack': 'WebTorrent*',
asar: {
// A glob expression, that unpacks the files with matching names to the
// "app.asar.unpacked" directory.
unpack: 'WebTorrent*'
},
// The build version of the application. Maps to the FileVersion metadata property on
// Windows, and CFBundleVersion on Mac. Note: Windows requires the build version to
// start with a number. We're using the version of the underlying WebTorrent library.
'build-version': require('webtorrent/package.json').version,
buildVersion: require('webtorrent/package.json').version,
// The application source directory.
dir: config.ROOT_PATH,
// Pattern which specifies which files to ignore when copying files to create the
// package(s).
ignore: /^\/src|^\/dist|\/(appveyor.yml|\.appveyor.yml|\.github|appdmg|AUTHORS|CONTRIBUTORS|bench|benchmark|benchmark\.js|bin|bower\.json|component\.json|coverage|doc|docs|docs\.mli|dragdrop\.min\.js|example|examples|example\.html|example\.js|externs|ipaddr\.min\.js|Makefile|min|minimist|perf|rusha|simplepeer\.min\.js|simplewebsocket\.min\.js|static\/screenshot\.png|test|tests|test\.js|tests\.js|webtorrent\.min\.js|\.[^\/]*|.*\.md|.*\.markdown)$/,
ignore: /^\/src|^\/dist|\/(appveyor.yml|\.appveyor.yml|\.github|appdmg|AUTHORS|CONTRIBUTORS|bench|benchmark|benchmark\.js|bin|bower\.json|component\.json|coverage|doc|docs|docs\.mli|dragdrop\.min\.js|example|examples|example\.html|example\.js|externs|ipaddr\.min\.js|Makefile|min|minimist|perf|rusha|simplepeer\.min\.js|simplewebsocket\.min\.js|static\/screenshot\.png|test|tests|test\.js|tests\.js|webtorrent\.min\.js|\.[^/]*|.*\.md|.*\.markdown)$/,
// The application name.
name: config.APP_NAME,
@@ -104,40 +109,40 @@ var all = {
// "devDependencies" before starting to package the app.
prune: true,
// The Electron version with which the app is built (without the leading 'v')
version: require('electron-prebuilt/package.json').version
// The Electron version that the app is built with (without the leading 'v')
electronVersion: require('electron/package.json').version
}
var darwin = {
const darwin = {
// Build for Mac
platform: 'darwin',
// Build 64 bit binaries only.
// Build x64 binary only.
arch: 'x64',
// The bundle identifier to use in the application's plist (Mac only).
'app-bundle-id': 'io.webtorrent.webtorrent',
appBundleId: 'io.webtorrent.webtorrent',
// The application category type, as shown in the Finder via "View" -> "Arrange by
// Application Category" when viewing the Applications directory (Mac only).
'app-category-type': 'public.app-category.utilities',
appCategoryType: 'public.app-category.utilities',
// The bundle identifier to use in the application helper's plist (Mac only).
'helper-bundle-id': 'io.webtorrent.webtorrent-helper',
helperBundleId: 'io.webtorrent.webtorrent-helper',
// Application icon.
icon: config.APP_ICON + '.icns'
}
var win32 = {
const win32 = {
// Build for Windows.
platform: 'win32',
// Build 32 bit binaries only.
arch: 'ia32',
// Build x64 binary only.
arch: 'x64',
// Object hash of application metadata to embed into the executable (Windows only)
'version-string': {
win32metadata: {
// Company that produced the file.
CompanyName: config.APP_NAME,
@@ -163,12 +168,12 @@ var win32 = {
icon: config.APP_ICON + '.ico'
}
var linux = {
const linux = {
// Build for Linux.
platform: 'linux',
// Build 32 and 64 bit binaries.
arch: 'all'
// Build x64, armv7l, and arm64 binaries.
arch: ['x64', 'armv7l', 'arm64']
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
}
@@ -176,31 +181,30 @@ var linux = {
build()
function buildDarwin (cb) {
var plist = require('plist')
const plist = require('plist')
console.log('Mac: Packaging electron...')
electronPackager(Object.assign({}, all, darwin), function (err, buildPath) {
if (err) return cb(err)
electronPackager(Object.assign({}, all, darwin)).then(function (buildPath) {
console.log('Mac: Packaged electron. ' + buildPath)
var appPath = path.join(buildPath[0], config.APP_NAME + '.app')
var contentsPath = path.join(appPath, 'Contents')
var resourcesPath = path.join(contentsPath, 'Resources')
var infoPlistPath = path.join(contentsPath, 'Info.plist')
var infoPlist = plist.parse(fs.readFileSync(infoPlistPath, 'utf8'))
const appPath = path.join(buildPath[0], config.APP_NAME + '.app')
const contentsPath = path.join(appPath, 'Contents')
const resourcesPath = path.join(contentsPath, 'Resources')
const infoPlistPath = path.join(contentsPath, 'Info.plist')
const infoPlist = plist.parse(fs.readFileSync(infoPlistPath, 'utf8'))
infoPlist.CFBundleDocumentTypes = [
{
CFBundleTypeExtensions: [ 'torrent' ],
CFBundleTypeExtensions: ['torrent'],
CFBundleTypeIconFile: path.basename(config.APP_FILE_ICON) + '.icns',
CFBundleTypeName: 'BitTorrent Document',
CFBundleTypeRole: 'Editor',
LSHandlerRank: 'Owner',
LSItemContentTypes: [ 'org.bittorrent.torrent' ]
LSItemContentTypes: ['org.bittorrent.torrent']
},
{
CFBundleTypeName: 'Any',
CFBundleTypeOSTypes: [ '****' ],
CFBundleTypeOSTypes: ['****'],
CFBundleTypeRole: 'Editor',
LSHandlerRank: 'Owner',
LSTypeIsPackage: false
@@ -212,13 +216,13 @@ function buildDarwin (cb) {
CFBundleTypeRole: 'Editor',
CFBundleURLIconFile: path.basename(config.APP_FILE_ICON) + '.icns',
CFBundleURLName: 'BitTorrent Magnet URL',
CFBundleURLSchemes: [ 'magnet' ]
CFBundleURLSchemes: ['magnet']
},
{
CFBundleTypeRole: 'Editor',
CFBundleURLIconFile: path.basename(config.APP_FILE_ICON) + '.icns',
CFBundleURLName: 'BitTorrent Stream-Magnet URL',
CFBundleURLSchemes: [ 'stream-magnet' ]
CFBundleURLSchemes: ['stream-magnet']
}
]
@@ -235,7 +239,7 @@ function buildDarwin (cb) {
UTTypeReferenceURL: 'http://www.bittorrent.org/beps/bep_0000.html',
UTTypeTagSpecification: {
'com.apple.ostype': 'TORR',
'public.filename-extension': [ 'torrent' ],
'public.filename-extension': ['torrent'],
'public.mime-type': 'application/x-bittorrent'
}
}
@@ -261,7 +265,8 @@ function buildDarwin (cb) {
}
function signApp (cb) {
var sign = require('electron-osx-sign')
const sign = require('electron-osx-sign')
const { notarize } = require('electron-notarize')
/*
* Sign the app with Apple Developer ID certificates. We sign the app for 2 reasons:
@@ -276,17 +281,38 @@ function buildDarwin (cb) {
* - Xcode Command Line Tools (xcode-select --install)
* - Membership in the Apple Developer Program
*/
var signOpts = {
const signOpts = {
verbose: true,
app: appPath,
platform: 'darwin',
verbose: true
identity: 'Developer ID Application: WebTorrent, LLC (5MAMC8G3L8)',
hardenedRuntime: true,
entitlements: path.join(config.ROOT_PATH, 'bin', 'darwin-entitlements.plist'),
'entitlements-inherit': path.join(config.ROOT_PATH, 'bin', 'darwin-entitlements.plist'),
'signature-flags': 'library'
}
const notarizeOpts = {
appBundleId: darwin.appBundleId,
appPath,
appleId: 'feross@feross.org',
appleIdPassword: '@keychain:AC_PASSWORD'
}
console.log('Mac: Signing app...')
sign(signOpts, function (err) {
if (err) return cb(err)
console.log('Mac: Signed app.')
cb(null)
console.log('Mac: Notarizing app...')
notarize(notarizeOpts).then(
function () {
console.log('Mac: Notarized app.')
cb(null)
},
function (err) {
cb(err)
})
})
}
@@ -302,8 +328,8 @@ function buildDarwin (cb) {
// Create .zip file (used by the auto-updater)
console.log('Mac: Creating zip...')
var inPath = path.join(buildPath[0], config.APP_NAME + '.app')
var outPath = path.join(DIST_PATH, BUILD_NAME + '-darwin.zip')
const inPath = path.join(buildPath[0], config.APP_NAME + '.app')
const outPath = path.join(DIST_PATH, BUILD_NAME + '-darwin.zip')
zip.zipSync(inPath, outPath)
console.log('Mac: Created zip.')
@@ -312,13 +338,13 @@ function buildDarwin (cb) {
function packageDmg (cb) {
console.log('Mac: Creating dmg...')
var appDmg = require('appdmg')
const appDmg = require('appdmg')
var targetPath = path.join(DIST_PATH, BUILD_NAME + '.dmg')
const targetPath = path.join(DIST_PATH, BUILD_NAME + '.dmg')
rimraf.sync(targetPath)
// Create a .dmg (Mac disk image) file, for easy user installation.
var dmgOpts = {
const dmgOpts = {
basepath: config.ROOT_PATH,
target: targetPath,
specification: {
@@ -339,7 +365,7 @@ function buildDarwin (cb) {
}
}
var dmg = appDmg(dmgOpts)
const dmg = appDmg(dmgOpts)
dmg.once('error', cb)
dmg.on('progress', function (info) {
if (info.type === 'step-begin') console.log(info.title + '...')
@@ -349,11 +375,13 @@ function buildDarwin (cb) {
cb(null)
})
}
}).catch(function (err) {
cb(err)
})
}
function buildWin32 (cb) {
var installer = require('electron-winstaller')
const installer = require('electron-winstaller')
console.log('Windows: Packaging electron...')
/*
@@ -361,7 +389,7 @@ function buildWin32 (cb) {
* - Windows Authenticode private key and cert (authenticode.p12)
* - Windows Authenticode password file (authenticode.txt)
*/
var CERT_PATH
let CERT_PATH
try {
fs.accessSync('D:')
CERT_PATH = 'D:'
@@ -369,16 +397,15 @@ function buildWin32 (cb) {
CERT_PATH = path.join(os.homedir(), 'Desktop')
}
electronPackager(Object.assign({}, all, win32), function (err, buildPath) {
if (err) return cb(err)
electronPackager(Object.assign({}, all, win32)).then(function (buildPath) {
console.log('Windows: Packaged electron. ' + buildPath)
var signWithParams
let signWithParams
if (process.platform === 'win32') {
if (argv.sign) {
var certificateFile = path.join(CERT_PATH, 'authenticode.p12')
var certificatePassword = fs.readFileSync(path.join(CERT_PATH, 'authenticode.txt'), 'utf8')
var timestampServer = 'http://timestamp.comodoca.com'
const certificateFile = path.join(CERT_PATH, 'authenticode.p12')
const certificatePassword = fs.readFileSync(path.join(CERT_PATH, 'authenticode.txt'), 'utf8')
const timestampServer = 'http://timestamp.comodoca.com'
signWithParams = `/a /f "${certificateFile}" /p "${certificatePassword}" /tr "${timestampServer}" /td sha256`
} else {
printWarning()
@@ -387,20 +414,22 @@ function buildWin32 (cb) {
printWarning()
}
var tasks = []
if (argv.package === 'exe' || argv.package === 'all') {
tasks.push((cb) => packageInstaller(cb))
}
if (argv.package === 'portable' || argv.package === 'all') {
tasks.push((cb) => packagePortable(cb))
}
const tasks = []
buildPath.forEach(function (filesPath) {
if (argv.package === 'exe' || argv.package === 'all') {
tasks.push((cb) => packageInstaller(filesPath, cb))
}
if (argv.package === 'portable' || argv.package === 'all') {
tasks.push((cb) => packagePortable(filesPath, cb))
}
})
series(tasks, cb)
function packageInstaller (cb) {
function packageInstaller (filesPath, cb) {
console.log('Windows: Creating installer...')
installer.createWindowsInstaller({
appDirectory: buildPath[0],
appDirectory: filesPath,
authors: config.APP_TEAM,
description: config.APP_NAME,
exe: config.APP_NAME + '.exe',
@@ -410,93 +439,174 @@ function buildWin32 (cb) {
noMsi: true,
outputDirectory: DIST_PATH,
productName: config.APP_NAME,
remoteReleases: config.GITHUB_URL,
// TODO: Re-enable Windows 64-bit delta updates when we confirm that they
// work correctly in the presence of the "ia32" .nupkg files. I
// (feross) noticed them listed in the 64-bit RELEASES file and
// manually edited them out for the v0.17 release. Shipping only
// full updates for now will work fine, with no ill-effects.
// remoteReleases: config.GITHUB_URL,
/**
* If you hit a "GitHub API rate limit exceeded" error, set this token!
*/
// remoteToken: process.env.WEBTORRENT_GITHUB_API_TOKEN,
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
setupIcon: config.APP_ICON + '.ico',
signWithParams: signWithParams,
signWithParams,
title: config.APP_NAME,
usePackageJson: false,
version: pkg.version
})
.then(function () {
console.log('Windows: Created installer.')
cb(null)
})
.catch(cb)
.then(function () {
console.log('Windows: Created installer.')
/**
* Delete extraneous Squirrel files (i.e. *.nupkg delta files for older
* versions of the app)
*/
fs.readdirSync(DIST_PATH)
.filter((name) => name.endsWith('.nupkg') && !name.includes(pkg.version))
.forEach((filename) => {
fs.unlinkSync(path.join(DIST_PATH, filename))
})
cb(null)
})
.catch(cb)
}
function packagePortable (cb) {
function packagePortable (filesPath, cb) {
console.log('Windows: Creating portable app...')
var portablePath = path.join(buildPath[0], 'Portable Settings')
mkdirp.sync(portablePath)
const portablePath = path.join(filesPath, 'Portable Settings')
fs.mkdirSync(portablePath, { recursive: true })
var inPath = path.join(DIST_PATH, path.basename(buildPath[0]))
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
const downloadsPath = path.join(portablePath, 'Downloads')
fs.mkdirSync(downloadsPath, { recursive: true })
const tempPath = path.join(portablePath, 'Temp')
fs.mkdirSync(tempPath, { recursive: true })
const inPath = path.join(DIST_PATH, path.basename(filesPath))
const outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
zip.zipSync(inPath, outPath)
console.log('Windows: Created portable app.')
cb(null)
}
}).catch(function (err) {
cb(err)
})
}
function buildLinux (cb) {
console.log('Linux: Packaging electron...')
electronPackager(Object.assign({}, all, linux), function (err, buildPath) {
if (err) return cb(err)
electronPackager(Object.assign({}, all, linux)).then(function (buildPath) {
console.log('Linux: Packaged electron. ' + buildPath)
var tasks = []
const tasks = []
buildPath.forEach(function (filesPath) {
var destArch = filesPath.split('-').pop()
const destArch = filesPath.split('-').pop()
if (argv.package === 'deb' || argv.package === 'all') {
tasks.push((cb) => packageDeb(filesPath, destArch, cb))
}
if (argv.package === 'rpm' || argv.package === 'all') {
tasks.push((cb) => packageRpm(filesPath, destArch, cb))
}
if (argv.package === 'zip' || argv.package === 'all') {
tasks.push((cb) => packageZip(filesPath, destArch, cb))
}
})
series(tasks, cb)
}).catch(function (err) {
cb(err)
})
function packageDeb (filesPath, destArch, cb) {
// Linux convention for Debian based 'x64' is 'amd64'
if (destArch === 'x64') {
destArch = 'amd64'
}
// Create .deb file for Debian-based platforms
console.log(`Linux: Creating ${destArch} deb...`)
var deb = require('nobin-debian-installer')()
var destPath = path.join('/opt', pkg.name)
const installer = require('electron-installer-debian')
deb.pack({
package: pkg,
info: {
arch: destArch === 'x64' ? 'amd64' : 'i386',
targetDir: DIST_PATH,
depends: 'gconf2, libgtk2.0-0, libnss3, libxss1',
scripts: {
postinst: path.join(config.STATIC_PATH, 'linux', 'postinst'),
prerm: path.join(config.STATIC_PATH, 'linux', 'prerm')
}
}
}, [{
src: ['./**'],
dest: destPath,
expand: true,
cwd: filesPath
}], function (err) {
if (err) return cb(err)
console.log(`Linux: Created ${destArch} deb.`)
cb(null)
})
const options = {
src: filesPath + '/',
dest: DIST_PATH,
arch: destArch,
bin: 'WebTorrent',
icon: {
'48x48': path.join(config.STATIC_PATH, 'linux/share/icons/hicolor/48x48/apps/webtorrent-desktop.png'),
'256x256': path.join(config.STATIC_PATH, 'linux/share/icons/hicolor/256x256/apps/webtorrent-desktop.png')
},
categories: ['Network', 'FileTransfer', 'P2P'],
mimeType: ['application/x-bittorrent', 'x-scheme-handler/magnet', 'x-scheme-handler/stream-magnet'],
desktopTemplate: path.join(config.STATIC_PATH, 'linux/webtorrent-desktop.ejs'),
lintianOverrides: [
'unstripped-binary-or-object',
'embedded-library',
'missing-dependency-on-libc',
'changelog-file-missing-in-native-package',
'description-synopsis-is-duplicated',
'setuid-binary',
'binary-without-manpage',
'shlib-with-executable-bit'
]
}
installer(options).then(
() => {
console.log(`Linux: Created ${destArch} deb.`)
cb(null)
},
(err) => cb(err)
)
}
function packageRpm (filesPath, destArch, cb) {
// Linux convention for RedHat based 'x64' is 'x86_64'
if (destArch === 'x64') {
destArch = 'x86_64'
}
// Create .rpm file for RedHat-based platforms
console.log(`Linux: Creating ${destArch} rpm...`)
const installer = require('electron-installer-redhat')
const options = {
src: filesPath + '/',
dest: DIST_PATH,
arch: destArch,
bin: 'WebTorrent',
icon: {
'48x48': path.join(config.STATIC_PATH, 'linux/share/icons/hicolor/48x48/apps/webtorrent-desktop.png'),
'256x256': path.join(config.STATIC_PATH, 'linux/share/icons/hicolor/256x256/apps/webtorrent-desktop.png')
},
categories: ['Network', 'FileTransfer', 'P2P'],
mimeType: ['application/x-bittorrent', 'x-scheme-handler/magnet', 'x-scheme-handler/stream-magnet'],
desktopTemplate: path.join(config.STATIC_PATH, 'linux/webtorrent-desktop.ejs')
}
installer(options).then(
() => {
console.log(`Linux: Created ${destArch} rpm.`)
cb(null)
},
(err) => cb(err)
)
}
function packageZip (filesPath, destArch, cb) {
// Create .zip file for Linux
console.log(`Linux: Creating ${destArch} zip...`)
var inPath = path.join(DIST_PATH, path.basename(filesPath))
var outPath = path.join(DIST_PATH, BUILD_NAME + '-linux-' + destArch + '.zip')
const inPath = path.join(DIST_PATH, path.basename(filesPath))
const outPath = path.join(DIST_PATH, `${BUILD_NAME}-linux-${destArch}.zip`)
zip.zipSync(inPath, outPath)
console.log(`Linux: Created ${destArch} zip.`)

View File

@@ -1,10 +0,0 @@
#!/bin/sh
set -e
npm run update-authors
git diff --exit-code
npm run package -- --sign
git push
git push --tags
npm publish
./node_modules/.bin/gh-release

View File

@@ -1,8 +0,0 @@
#!/bin/sh
set -e
git pull
rm -rf node_modules/
npm install
npm dedupe
npm test

View File

@@ -1,7 +0,0 @@
#!/bin/sh
set -e
BIN=`dirname $0`
$BIN/release-_pre.sh
npm version major
$BIN/release-_post.sh

View File

@@ -1,7 +0,0 @@
#!/bin/sh
set -e
BIN=`dirname $0`
$BIN/release-_pre.sh
npm version minor
$BIN/release-_post.sh

View File

@@ -1,7 +0,0 @@
#!/bin/sh
set -e
BIN=`dirname $0`
$BIN/release-_pre.sh
npm version patch
$BIN/release-_post.sh

View File

@@ -12,6 +12,8 @@ while (<>) {
next if /(ungoldman\@gmail.com)/;
next if /(dc\@DCs-MacBook.local)/;
next if /(rolandoguedes\@gmail.com)/;
next if /(grunjol\@users.noreply.github.com)/;
next if /(dependabot)/;
$seen{$_} = push @authors, "- ", $_;
}
END {

27431
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,69 +1,88 @@
{
"name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
"version": "0.11.0",
"version": "0.24.0",
"author": {
"name": "WebTorrent, LLC",
"email": "feross@webtorrent.io",
"url": "https://webtorrent.io"
},
"bin": {
"webtorrent-desktop": "./bin/cmd.js"
"babel": {
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"useBuiltIns": true
}
]
]
},
"bugs": {
"url": "https://github.com/feross/webtorrent-desktop/issues"
"url": "https://github.com/webtorrent/webtorrent-desktop/issues"
},
"dependencies": {
"airplayer": "^2.0.0",
"application-config": "^1.0.0",
"bitfield": "^1.0.2",
"chromecasts": "^1.8.0",
"create-torrent": "^3.24.5",
"deep-equal": "^1.0.1",
"dlnacasts": "^0.1.0",
"drag-drop": "^2.12.1",
"electron-prebuilt": "1.3.3",
"fs-extra": "^0.30.0",
"hat": "0.0.3",
"iso-639-1": "^1.2.1",
"languagedetect": "^1.1.1",
"location-history": "^1.0.0",
"musicmetadata": "^2.0.2",
"network-address": "^1.1.0",
"parse-torrent": "^5.7.3",
"prettier-bytes": "^1.0.1",
"react": "^15.2.1",
"react-dom": "^15.2.1",
"run-parallel": "^1.1.6",
"semver": "^5.1.0",
"simple-concat": "^1.0.0",
"simple-get": "^2.0.0",
"srt-to-vtt": "^1.1.1",
"vlc-command": "^1.0.1",
"webtorrent": "0.x",
"winreg": "^1.2.0",
"zero-fill": "^2.2.3"
"@electron/remote": "2.1.3",
"airplayer": "github:webtorrent/airplayer#fix-security",
"application-config": "2.0.0",
"arch": "2.2.0",
"auto-launch": "5.0.5",
"bitfield": "4.1.0",
"capture-frame": "4.0.0",
"chokidar": "3.5.3",
"chromecasts": "1.10.2",
"create-torrent": "5.0.9",
"debounce": "1.2.1",
"dlnacasts": "0.1.0",
"drag-drop": "7.2.0",
"es6-error": "4.1.1",
"fn-getter": "1.0.0",
"iso-639-1": "2.1.15",
"languagedetect": "2.0.0",
"location-history": "1.1.2",
"material-ui": "0.20.2",
"music-metadata": "7.14.0",
"network-address": "1.1.2",
"parse-torrent": "9.1.5",
"prettier-bytes": "1.0.4",
"prop-types": "15.8.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"rimraf": "4.4.0",
"run-parallel": "1.2.0",
"semver": "7.3.8",
"simple-concat": "1.0.1",
"simple-get": "4.0.1",
"srt-to-vtt": "1.1.3",
"vlc-command": "1.2.0",
"webtorrent": "1.9.7",
"winreg": "1.2.4"
},
"devDependencies": {
"babel-cli": "^6.11.4",
"babel-plugin-syntax-jsx": "^6.13.0",
"babel-plugin-transform-react-jsx": "^6.8.0",
"cross-zip": "^2.0.1",
"electron-osx-sign": "^0.3.0",
"electron-packager": "^7.0.0",
"electron-winstaller": "^2.3.0",
"gh-release": "^2.0.3",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nobin-debian-installer": "^0.0.10",
"open": "0.0.5",
"plist": "^2.0.1",
"rimraf": "^2.5.2",
"run-series": "^1.1.4",
"standard": "^8.0.0-beta.5"
"@babel/cli": "7.28.6",
"@babel/core": "7.29.0",
"@babel/eslint-parser": "7.28.6",
"@babel/plugin-transform-react-jsx": "7.28.6",
"cross-zip": "4.0.0",
"depcheck": "1.4.7",
"electron": "27.3.11",
"electron-notarize": "1.2.2",
"electron-osx-sign": "0.6.0",
"electron-packager": "17.1.2",
"electron-winstaller": "5.4.0",
"gh-release": "7.0.2",
"minimist": "1.2.8",
"nodemon": "2.0.22",
"open": "8.4.2",
"plist": "3.1.0",
"pngjs": "7.0.0",
"run-series": "1.1.9",
"spectron": "19.0.0",
"standard": "17.0.0",
"tape": "5.9.0"
},
"engines": {
"node": ">=4.0.0"
"node": "^16.0.0 || ^18.0.0",
"npm": "^7.10.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
},
"homepage": "https://webtorrent.io",
"keywords": [
@@ -79,21 +98,35 @@
"license": "MIT",
"main": "index.js",
"optionalDependencies": {
"appdmg": "^0.4.3"
"appdmg": "^0.6.0",
"electron-installer-debian": "^3.2.0",
"electron-installer-redhat": "^3.4.0"
},
"private": true,
"productName": "WebTorrent",
"repository": {
"type": "git",
"url": "git://github.com/feross/webtorrent-desktop.git"
"url": "git://github.com/webtorrent/webtorrent-desktop.git"
},
"scripts": {
"build": "babel --quiet src --out-dir build",
"build": "babel src --out-dir build",
"clean": "node ./bin/clean.js",
"gh-release": "gh-release",
"install-system-deps": "brew install fakeroot dpkg rpm",
"open-config": "node ./bin/open-config.js",
"package": "node ./bin/package.js",
"prepublish": "npm run build",
"start": "npm run build && electron .",
"test": "standard && node ./bin/check-deps.js",
"update-authors": "./bin/update-authors.sh"
"start": "npm run build && electron --no-sandbox .",
"test": "standard && depcheck --ignores=standard,@babel/eslint-parser --ignore-dirs=build,dist",
"test-integration": "npm run build && node ./test",
"update-authors": "./bin/update-authors.sh",
"watch": "nodemon --exec \"npm run start\" --ext js,css --ignore build/ --ignore dist/"
},
"standard": {
"parser": "@babel/eslint-parser"
},
"renovate": {
"extends": [
"github>webtorrent/renovate-config"
]
}
}

View File

@@ -1,6 +0,0 @@
{
"plugins": [
"syntax-jsx",
"transform-react-jsx"
]
}

View File

@@ -1,12 +1,21 @@
var appConfig = require('application-config')('WebTorrent')
var fs = require('fs')
var path = require('path')
const appConfig = require('application-config')('WebTorrent')
const path = require('path')
const { app } = require('electron')
const arch = require('arch')
var APP_NAME = 'WebTorrent'
var APP_TEAM = 'WebTorrent, LLC'
var APP_VERSION = require('../package.json').version
const APP_NAME = 'WebTorrent'
const APP_TEAM = 'WebTorrent, LLC'
const APP_VERSION = require('../package.json').version
var PORTABLE_PATH = path.join(path.dirname(process.execPath), 'Portable Settings')
const IS_TEST = isTest()
const PORTABLE_PATH = IS_TEST
? path.join(process.platform === 'win32' ? 'C:\\Windows\\Temp' : '/tmp', 'WebTorrentTest')
: path.join(path.dirname(process.execPath), 'Portable Settings')
const IS_PRODUCTION = isProduction()
const IS_PORTABLE = isPortable()
const UI_HEADER_HEIGHT = 38
const UI_TORRENT_HEIGHT = 100
module.exports = {
ANNOUNCEMENT_URL: 'https://webtorrent.io/desktop/announcement',
@@ -14,39 +23,44 @@ module.exports = {
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
TELEMETRY_URL: 'https://webtorrent.io/desktop/telemetry',
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
APP_COPYRIGHT: `Copyright © 2014-${new Date().getFullYear()} ${APP_TEAM}`,
APP_FILE_ICON: path.join(__dirname, '..', 'static', 'WebTorrentFile'),
APP_ICON: path.join(__dirname, '..', 'static', 'WebTorrent'),
APP_NAME: APP_NAME,
APP_TEAM: APP_TEAM,
APP_VERSION: APP_VERSION,
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
APP_NAME,
APP_TEAM,
APP_VERSION,
APP_WINDOW_TITLE: APP_NAME,
CONFIG_PATH: getConfigPath(),
DEFAULT_TORRENTS: [
{
testID: 'bbb',
name: 'Big Buck Bunny',
posterFileName: 'bigBuckBunny.jpg',
torrentFileName: 'bigBuckBunny.torrent'
},
{
testID: 'cosmos',
name: 'Cosmos Laundromat (Preview)',
posterFileName: 'cosmosLaundromat.jpg',
torrentFileName: 'cosmosLaundromat.torrent'
},
{
testID: 'sintel',
name: 'Sintel',
posterFileName: 'sintel.jpg',
torrentFileName: 'sintel.torrent'
},
{
testID: 'tears',
name: 'Tears of Steel',
posterFileName: 'tearsOfSteel.jpg',
torrentFileName: 'tearsOfSteel.torrent'
},
{
name: 'The WIRED CD - Rip. Sample. Mash. Share.',
testID: 'wired',
name: 'The WIRED CD - Rip. Sample. Mash. Share',
posterFileName: 'wiredCd.jpg',
torrentFileName: 'wiredCd.torrent'
}
@@ -56,14 +70,19 @@ module.exports = {
DEFAULT_DOWNLOAD_PATH: getDefaultDownloadPath(),
GITHUB_URL: 'https://github.com/feross/webtorrent-desktop',
GITHUB_URL_ISSUES: 'https://github.com/feross/webtorrent-desktop/issues',
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/feross/webtorrent-desktop/master',
GITHUB_URL: 'https://github.com/webtorrent/webtorrent-desktop',
GITHUB_URL_ISSUES: 'https://github.com/webtorrent/webtorrent-desktop/issues',
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/webtorrent/webtorrent-desktop/master',
GITHUB_URL_RELEASES: 'https://github.com/webtorrent/webtorrent-desktop/releases',
HOME_PAGE_URL: 'https://webtorrent.io',
TWITTER_PAGE_URL: 'https://twitter.com/WebTorrentApp',
IS_PORTABLE: isPortable(),
IS_PRODUCTION: isProduction(),
IS_PORTABLE,
IS_PRODUCTION,
IS_TEST,
OS_SYSARCH: arch() === 'x64' ? 'x64' : 'ia32',
POSTER_PATH: path.join(getConfigPath(), 'Posters'),
ROOT_PATH: path.join(__dirname, '..'),
@@ -74,12 +93,19 @@ module.exports = {
WINDOW_MAIN: 'file://' + path.join(__dirname, '..', 'static', 'main.html'),
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, '..', 'static', 'webtorrent.html'),
WINDOW_MIN_HEIGHT: 38 + (120 * 2), // header height + 2 torrents
WINDOW_MIN_WIDTH: 425
WINDOW_INITIAL_BOUNDS: {
width: 500,
height: UI_HEADER_HEIGHT + (UI_TORRENT_HEIGHT * 6) // header + 6 torrents
},
WINDOW_MIN_HEIGHT: UI_HEADER_HEIGHT + (UI_TORRENT_HEIGHT * 2), // header + 2 torrents
WINDOW_MIN_WIDTH: 425,
UI_HEADER_HEIGHT,
UI_TORRENT_HEIGHT
}
function getConfigPath () {
if (isPortable()) {
if (IS_PORTABLE) {
return PORTABLE_PATH
} else {
return path.dirname(appConfig.filePath)
@@ -87,24 +113,47 @@ function getConfigPath () {
}
function getDefaultDownloadPath () {
if (!process || !process.type) {
return ''
}
if (isPortable()) {
if (IS_PORTABLE) {
return path.join(getConfigPath(), 'Downloads')
} else {
return getPath('downloads')
}
}
var electron = require('electron')
function getPath (key) {
if (!process.versions.electron) {
// Node.js process
return ''
} else if (process.type === 'renderer') {
// Electron renderer process
return require('@electron/remote').app.getPath(key)
} else {
// Electron main process
return app.getPath(key)
}
}
return process.type === 'renderer'
? electron.remote.app.getPath('downloads')
: electron.app.getPath('downloads')
function isTest () {
return process.env.NODE_ENV === 'test'
}
function isPortable () {
if (IS_TEST) {
return true
}
if (process.platform !== 'win32' || !IS_PRODUCTION) {
// Fast path: Non-Windows platforms should not check for path on disk
return false
}
const fs = require('fs')
try {
return process.platform === 'win32' && isProduction() && !!fs.statSync(PORTABLE_PATH)
// This line throws if the "Portable Settings" folder does not exist, and does
// nothing otherwise.
fs.accessSync(PORTABLE_PATH, fs.constants.R_OK | fs.constants.W_OK)
return true
} catch (err) {
return false
}
@@ -112,6 +161,7 @@ function isPortable () {
function isProduction () {
if (!process.versions.electron) {
// Node.js process
return false
}
if (process.platform === 'darwin') {

View File

@@ -2,13 +2,14 @@ module.exports = {
init
}
var config = require('./config')
var electron = require('electron')
function init () {
electron.crashReporter.start({
companyName: config.APP_NAME,
const config = require('./config')
const { crashReporter } = require('electron')
crashReporter.start({
productName: config.APP_NAME,
submitURL: config.CRASH_REPORT_URL
submitURL: config.CRASH_REPORT_URL,
globalExtra: { _companyName: config.APP_NAME },
compress: true
})
}

View File

@@ -2,14 +2,13 @@ module.exports = {
init
}
var electron = require('electron')
const { dialog } = require('electron')
var config = require('../config')
var log = require('./log')
const config = require('../config')
const log = require('./log')
var ANNOUNCEMENT_URL = config.ANNOUNCEMENT_URL +
'?version=' + config.APP_VERSION +
'&platform=' + process.platform
const ANNOUNCEMENT_URL =
`${config.ANNOUNCEMENT_URL}?version=${config.APP_VERSION}&platform=${process.platform}`
/**
* In certain situations, the WebTorrent team may need to show an announcement to
@@ -26,7 +25,7 @@ var ANNOUNCEMENT_URL = config.ANNOUNCEMENT_URL +
* }
*/
function init () {
var get = require('simple-get')
const get = require('simple-get')
get.concat(ANNOUNCEMENT_URL, onResponse)
}
@@ -45,13 +44,11 @@ function onResponse (err, res, data) {
}
}
electron.dialog.showMessageBox({
dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
title: data.title,
message: data.message,
detail: data.detail
}, noop)
})
}
function noop () {}

View File

@@ -6,10 +6,10 @@ module.exports = {
openFiles
}
var electron = require('electron')
const { dialog } = require('electron')
var log = require('./log')
var windows = require('./windows')
const log = require('./log')
const windows = require('./windows')
/**
* Show open dialog to create a single-file torrent.
@@ -17,16 +17,11 @@ var windows = require('./windows')
function openSeedFile () {
if (!windows.main.win) return
log('openSeedFile')
var opts = {
const opts = {
title: 'Select a file for the torrent.',
properties: [ 'openFile' ]
properties: ['openFile']
}
setTitle(opts.title)
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('showCreateTorrent', selectedPaths)
})
showOpenSeed(opts)
}
/*
@@ -37,21 +32,16 @@ function openSeedFile () {
function openSeedDirectory () {
if (!windows.main.win) return
log('openSeedDirectory')
var opts = process.platform === 'darwin'
const opts = process.platform === 'darwin'
? {
title: 'Select a file or folder for the torrent.',
properties: [ 'openFile', 'openDirectory' ]
}
title: 'Select a file or folder for the torrent.',
properties: ['openFile', 'openDirectory']
}
: {
title: 'Select a folder for the torrent.',
properties: [ 'openDirectory' ]
}
setTitle(opts.title)
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('showCreateTorrent', selectedPaths)
})
title: 'Select a folder for the torrent.',
properties: ['openDirectory']
}
showOpenSeed(opts)
}
/*
@@ -61,21 +51,20 @@ function openSeedDirectory () {
function openFiles () {
if (!windows.main.win) return
log('openFiles')
var opts = process.platform === 'darwin'
const opts = process.platform === 'darwin'
? {
title: 'Select a file or folder to add.',
properties: [ 'openFile', 'openDirectory' ]
}
title: 'Select a file or folder to add.',
properties: ['openFile', 'openDirectory']
}
: {
title: 'Select a file to add.',
properties: [ 'openFile' ]
}
title: 'Select a file to add.',
properties: ['openFile']
}
setTitle(opts.title)
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('onOpen', selectedPaths)
})
const selectedPaths = dialog.showOpenDialogSync(windows.main.win, opts)
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('onOpen', selectedPaths)
}
/*
@@ -84,18 +73,17 @@ function openFiles () {
function openTorrentFile () {
if (!windows.main.win) return
log('openTorrentFile')
var opts = {
const opts = {
title: 'Select a .torrent file.',
filters: [{ name: 'Torrent Files', extensions: ['torrent'] }],
properties: [ 'openFile', 'multiSelections' ]
properties: ['openFile', 'multiSelections']
}
setTitle(opts.title)
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
resetTitle()
if (!Array.isArray(selectedPaths)) return
selectedPaths.forEach(function (selectedPath) {
windows.main.dispatch('addTorrent', selectedPath)
})
const selectedPaths = dialog.showOpenDialogSync(windows.main.win, opts)
resetTitle()
if (!Array.isArray(selectedPaths)) return
selectedPaths.forEach(selectedPath => {
windows.main.dispatch('addTorrent', selectedPath)
})
}
@@ -119,3 +107,15 @@ function setTitle (title) {
function resetTitle () {
windows.main.dispatch('resetTitle')
}
/**
* Pops up an Open File dialog with the given options.
* After the user selects files / folders, shows the Create Torrent page.
*/
function showOpenSeed (opts) {
setTitle(opts.title)
const selectedPaths = dialog.showOpenDialogSync(windows.main.win, opts)
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('showCreateTorrent', selectedPaths)
}

View File

@@ -4,19 +4,17 @@ module.exports = {
setBadge
}
var electron = require('electron')
const { app, Menu } = require('electron')
var app = electron.app
var dialog = require('./dialog')
var log = require('./log')
const dialog = require('./dialog')
const log = require('./log')
/**
* Add a right-click menu to the dock icon. (Mac)
*/
function init () {
if (!app.dock) return
var menu = electron.Menu.buildFromTemplate(getMenuTemplate())
const menu = Menu.buildFromTemplate(getMenuTemplate())
app.dock.setMenu(menu)
}
@@ -33,8 +31,11 @@ function downloadFinished (path) {
* Display a counter badge for the app. (Mac, Linux)
*/
function setBadge (count) {
log(`setBadge: ${count}`)
app.setBadgeCount(Number(count))
if (process.platform === 'darwin' ||
(process.platform === 'linux' && app.isUnityRunning())) {
log(`setBadge: ${count}`)
app.badgeCount = Number(count)
}
}
function getMenuTemplate () {

View File

@@ -0,0 +1,76 @@
module.exports = {
spawn,
kill,
checkInstall
}
const cp = require('child_process')
const path = require('path')
const vlcCommand = require('vlc-command')
const log = require('./log')
const windows = require('./windows')
// holds a ChildProcess while we're playing a video in an external player, null otherwise
let proc = null
function checkInstall (playerPath, cb) {
// check for VLC if external player has not been specified by the user
// otherwise assume the player is installed
if (!playerPath) return vlcCommand(cb)
process.nextTick(() => cb(null))
}
function spawn (playerPath, url, title) {
if (playerPath) return spawnExternal(playerPath, [url])
// Try to find and use VLC if external player is not specified
vlcCommand((err, vlcPath) => {
if (err) return windows.main.dispatch('externalPlayerNotFound')
const args = [
'--play-and-exit',
'--quiet',
`--meta-title=${JSON.stringify(title)}`,
url
]
spawnExternal(vlcPath, args)
})
}
function kill () {
if (!proc) return
log(`Killing external player, pid ${proc.pid}`)
proc.kill('SIGKILL') // kill -9
proc = null
}
function spawnExternal (playerPath, args) {
log('Running external media player:', `${playerPath} ${args.join(' ')}`)
if (process.platform === 'darwin' && path.extname(playerPath) === '.app') {
// Mac: Use executable in packaged .app bundle
playerPath += `/Contents/MacOS/${path.basename(playerPath, '.app')}`
}
proc = cp.spawn(playerPath, args, { stdio: 'ignore' })
// If it works, close the modal after a second
const closeModalTimeout = setTimeout(() =>
windows.main.dispatch('exitModal'), 1000)
proc.on('close', code => {
clearTimeout(closeModalTimeout)
if (!proc) return // Killed
log('External player exited with code ', code)
if (code === 0) {
windows.main.dispatch('backToList')
} else {
windows.main.dispatch('externalPlayerNotFound')
}
proc = null
})
proc.on('error', err => {
log('External player error', err)
})
}

View File

@@ -0,0 +1,50 @@
const chokidar = require('chokidar')
const log = require('./log')
class FolderWatcher {
constructor ({ window, state }) {
this.window = window
this.state = state
this.torrentsFolderPath = null
this.watching = false
}
isEnabled () {
return this.state.saved.prefs.autoAddTorrents
}
start () {
// Stop watching previous folder before
// start watching a new one.
if (this.watching) this.stop()
const torrentsFolderPath = this.state.saved.prefs.torrentsFolderPath
this.torrentsFolderPath = torrentsFolderPath
if (!torrentsFolderPath) return
const glob = `${torrentsFolderPath}/**/*.torrent`
log('Folder Watcher: watching: ', glob)
const options = {
ignoreInitial: true,
awaitWriteFinish: true
}
this.watcher = chokidar.watch(glob, options)
this.watcher
.on('add', (path) => {
log('Folder Watcher: added torrent: ', path)
this.window.dispatch('addTorrent', path)
})
this.watching = true
}
stop () {
log('Folder Watcher: stop.')
if (!this.watching) return
this.watcher.close()
this.watching = false
}
}
module.exports = FolderWatcher

View File

@@ -3,36 +3,29 @@ module.exports = {
uninstall
}
var config = require('../config')
var path = require('path')
const config = require('../config')
const path = require('path')
function install () {
if (process.platform === 'darwin') {
installDarwin()
}
if (process.platform === 'win32') {
installWin32()
}
if (process.platform === 'linux') {
installLinux()
switch (process.platform) {
case 'darwin': installDarwin()
break
case 'win32': installWin32()
break
}
}
function uninstall () {
if (process.platform === 'darwin') {
uninstallDarwin()
}
if (process.platform === 'win32') {
uninstallWin32()
}
if (process.platform === 'linux') {
uninstallLinux()
switch (process.platform) {
case 'darwin': uninstallDarwin()
break
case 'win32': uninstallWin32()
break
}
}
function installDarwin () {
var electron = require('electron')
var app = electron.app
const { app } = require('electron')
// On Mac, only protocols that are listed in `Info.plist` can be set as the
// default handler at runtime.
@@ -44,18 +37,18 @@ function installDarwin () {
function uninstallDarwin () {}
var EXEC_COMMAND = [ process.execPath ]
const EXEC_COMMAND = [process.execPath, '--']
if (!config.IS_PRODUCTION) {
EXEC_COMMAND.push(config.ROOT_PATH)
}
function installWin32 () {
var Registry = require('winreg')
const Registry = require('winreg')
var log = require('./log')
const log = require('./log')
var iconPath = path.join(
const iconPath = path.join(
process.resourcesPath, 'app.asar.unpacked', 'static', 'WebTorrentFile.ico'
)
registerProtocolHandlerWin32(
@@ -100,7 +93,7 @@ function installWin32 () {
*/
function registerProtocolHandlerWin32 (protocol, name, icon, command) {
var protocolKey = new Registry({
const protocolKey = new Registry({
hive: Registry.HKCU, // HKEY_CURRENT_USER
key: '\\Software\\Classes\\' + protocol
})
@@ -108,37 +101,37 @@ function installWin32 () {
setProtocol()
function setProtocol (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
protocolKey.set('', Registry.REG_SZ, name, setURLProtocol)
}
function setURLProtocol (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
protocolKey.set('URL Protocol', Registry.REG_SZ, '', setIcon)
}
function setIcon (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
var iconKey = new Registry({
const iconKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + protocol + '\\DefaultIcon'
key: `\\Software\\Classes\\${protocol}\\DefaultIcon`
})
iconKey.set('', Registry.REG_SZ, icon, setCommand)
}
function setCommand (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
var commandKey = new Registry({
const commandKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
key: `\\Software\\Classes\\${protocol}\\shell\\open\\command`
})
commandKey.set('', Registry.REG_SZ, `${commandToArgs(command)} "%1"`, done)
}
function done (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
}
}
@@ -161,51 +154,51 @@ function installWin32 () {
setExt()
function setExt () {
var extKey = new Registry({
const extKey = new Registry({
hive: Registry.HKCU, // HKEY_CURRENT_USER
key: '\\Software\\Classes\\' + ext
key: `\\Software\\Classes\\${ext}`
})
extKey.set('', Registry.REG_SZ, id, setId)
}
function setId (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
var idKey = new Registry({
const idKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + id
key: `\\Software\\Classes\\${id}`
})
idKey.set('', Registry.REG_SZ, name, setIcon)
}
function setIcon (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
var iconKey = new Registry({
const iconKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + id + '\\DefaultIcon'
key: `\\Software\\Classes\\${id}\\DefaultIcon`
})
iconKey.set('', Registry.REG_SZ, icon, setCommand)
}
function setCommand (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
var commandKey = new Registry({
const commandKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + id + '\\shell\\open\\command'
key: `\\Software\\Classes\\${id}\\shell\\open\\command`
})
commandKey.set('', Registry.REG_SZ, `${commandToArgs(command)} "%1"`, done)
}
function done (err) {
if (err) log.error(err.message)
if (err) return log.error(err.message)
}
}
}
function uninstallWin32 () {
var Registry = require('winreg')
const Registry = require('winreg')
unregisterProtocolHandlerWin32('magnet', EXEC_COMMAND)
unregisterProtocolHandlerWin32('stream-magnet', EXEC_COMMAND)
@@ -215,11 +208,11 @@ function uninstallWin32 () {
getCommand()
function getCommand () {
var commandKey = new Registry({
const commandKey = new Registry({
hive: Registry.HKCU, // HKEY_CURRENT_USER
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
key: `\\Software\\Classes\\${protocol}\\shell\\open\\command`
})
commandKey.get('', function (err, item) {
commandKey.get('', (err, item) => {
if (!err && item.value.indexOf(commandToArgs(command)) >= 0) {
destroyProtocol()
}
@@ -227,11 +220,11 @@ function uninstallWin32 () {
}
function destroyProtocol () {
var protocolKey = new Registry({
const protocolKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + protocol
key: `\\Software\\Classes\\${protocol}`
})
protocolKey.destroy(function () {})
protocolKey.destroy(() => {})
}
}
@@ -239,19 +232,19 @@ function uninstallWin32 () {
eraseId()
function eraseId () {
var idKey = new Registry({
const idKey = new Registry({
hive: Registry.HKCU, // HKEY_CURRENT_USER
key: '\\Software\\Classes\\' + id
key: `\\Software\\Classes\\${id}`
})
idKey.destroy(getExt)
}
function getExt () {
var extKey = new Registry({
const extKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + ext
key: `\\Software\\Classes\\${ext}`
})
extKey.get('', function (err, item) {
extKey.get('', (err, item) => {
if (!err && item.value === id) {
destroyExt()
}
@@ -259,11 +252,11 @@ function uninstallWin32 () {
}
function destroyExt () {
var extKey = new Registry({
const extKey = new Registry({
hive: Registry.HKCU, // HKEY_CURRENT_USER
key: '\\Software\\Classes\\' + ext
key: `\\Software\\Classes\\${ext}`
})
extKey.destroy(function () {})
extKey.destroy(() => {})
}
}
}
@@ -271,92 +264,3 @@ function uninstallWin32 () {
function commandToArgs (command) {
return command.map((arg) => `"${arg}"`).join(' ')
}
function installLinux () {
var fs = require('fs-extra')
var os = require('os')
var path = require('path')
var config = require('../config')
var log = require('./log')
installDesktopFile()
installIconFile()
function installDesktopFile () {
var templatePath = path.join(
config.STATIC_PATH, 'linux', 'webtorrent-desktop.desktop'
)
fs.readFile(templatePath, 'utf8', writeDesktopFile)
}
function writeDesktopFile (err, desktopFile) {
if (err) return log.error(err.message)
var appPath = config.IS_PRODUCTION
? path.dirname(process.execPath)
: config.ROOT_PATH
desktopFile = desktopFile.replace(/\$APP_NAME/g, config.APP_NAME)
desktopFile = desktopFile.replace(/\$APP_PATH/g, appPath)
desktopFile = desktopFile.replace(/\$EXEC_PATH/g, EXEC_COMMAND.join(' '))
desktopFile = desktopFile.replace(/\$TRY_EXEC_PATH/g, process.execPath)
var desktopFilePath = path.join(
os.homedir(),
'.local',
'share',
'applications',
'webtorrent-desktop.desktop'
)
fs.mkdirp(path.dirname(desktopFilePath))
fs.writeFile(desktopFilePath, desktopFile, function (err) {
if (err) return log.error(err.message)
})
}
function installIconFile () {
var iconStaticPath = path.join(config.STATIC_PATH, 'WebTorrent.png')
fs.readFile(iconStaticPath, writeIconFile)
}
function writeIconFile (err, iconFile) {
if (err) return log.error(err.message)
var iconFilePath = path.join(
os.homedir(),
'.local',
'share',
'icons',
'webtorrent-desktop.png'
)
fs.mkdirp(path.dirname(iconFilePath))
fs.writeFile(iconFilePath, iconFile, function (err) {
if (err) return log.error(err.message)
})
}
}
function uninstallLinux () {
var os = require('os')
var path = require('path')
var fs = require('fs-extra')
var desktopFilePath = path.join(
os.homedir(),
'.local',
'share',
'applications',
'webtorrent-desktop.desktop'
)
fs.removeSync(desktopFilePath)
var iconFilePath = path.join(
os.homedir(),
'.local',
'share',
'icons',
'webtorrent-desktop.png'
)
fs.removeSync(iconFilePath)
}

View File

@@ -1,26 +1,33 @@
console.time('init')
var electron = require('electron')
require('@electron/remote/main').initialize()
const { app, ipcMain } = require('electron')
var app = electron.app
var ipcMain = electron.ipcMain
// Start crash reporter early, so it takes effect for child processes
const crashReporter = require('../crash-reporter')
crashReporter.init()
var announcement = require('./announcement')
var config = require('../config')
var crashReporter = require('../crash-reporter')
var dialog = require('./dialog')
var dock = require('./dock')
var ipc = require('./ipc')
var log = require('./log')
var menu = require('./menu')
var squirrelWin32 = require('./squirrel-win32')
var tray = require('./tray')
var updater = require('./updater')
var userTasks = require('./user-tasks')
var windows = require('./windows')
const parallel = require('run-parallel')
var shouldQuit = false
var argv = sliceArgv(process.argv)
const config = require('../config')
const ipc = require('./ipc')
const log = require('./log')
const menu = require('./menu')
const State = require('../renderer/lib/state')
const windows = require('./windows')
const WEBTORRENT_VERSION = require('webtorrent/package.json').version
let shouldQuit = false
let argv = sliceArgv(process.argv)
// allow electron/chromium to play startup sounds (without user interaction)
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required')
// Start the app without showing the main window when auto launching on login
// (On Windows and Linux, we get a flag. On MacOS, we get special API.)
const hidden = argv.includes('--hidden') ||
(process.platform === 'darwin' && app.getLoginItemSettings().wasOpenedAsHidden)
if (config.IS_PRODUCTION) {
// When Electron is running in production mode (packaged app), then run React
@@ -29,90 +36,131 @@ if (config.IS_PRODUCTION) {
}
if (process.platform === 'win32') {
const squirrelWin32 = require('./squirrel-win32')
shouldQuit = squirrelWin32.handleEvent(argv[0])
argv = argv.filter((arg) => !arg.includes('--squirrel'))
}
if (!shouldQuit) {
// Prevent multiple instances of app from running at same time. New instances signal
// this instance and quit.
shouldQuit = app.makeSingleInstance(onAppOpen)
if (shouldQuit) {
app.quit()
if (!shouldQuit && !config.IS_PORTABLE) {
// Prevent multiple instances of app from running at same time. New instances
// signal this instance and quit. Note: This feature creates a lock file in
// %APPDATA%\Roaming\WebTorrent so we do not do it for the Portable App since
// we want to be "silent" as well as "portable".
if (!app.requestSingleInstanceLock()) {
shouldQuit = true
}
}
if (!shouldQuit) {
if (shouldQuit) {
app.quit()
} else {
init()
}
function init () {
app.on('second-instance', (event, commandLine, workingDirectory) => onAppOpen(commandLine))
if (config.IS_PORTABLE) {
const path = require('path')
// Put all user data into the "Portable Settings" folder
app.setPath('userData', config.CONFIG_PATH)
// Put Electron crash files, etc. into the "Portable Settings\Temp" folder
app.setPath('temp', path.join(config.CONFIG_PATH, 'Temp'))
}
var isReady = false // app ready, windows can be created
let isReady = false // app ready, windows can be created
app.ipcReady = false // main window has finished loading and IPC is ready
app.isQuitting = false
// Open handlers must be added as early as possible
parallel({
appReady: (cb) => app.on('ready', () => cb(null)),
state: (cb) => State.load(cb)
}, onReady)
function onReady (err, results) {
if (err) throw err
isReady = true
const state = results.state
menu.init()
windows.main.init(state, { hidden })
windows.webtorrent.init()
// To keep app startup fast, some code is delayed.
setTimeout(() => {
delayedInit(state)
}, config.DELAYED_INIT)
// Report uncaught exceptions
process.on('uncaughtException', (err) => {
console.error(err)
const error = { message: err.message, stack: err.stack }
windows.main.dispatch('uncaughtError', 'main', error)
})
}
// Enable app logging into default directory, i.e. /Library/Logs/WebTorrent
// on Mac, %APPDATA% on Windows, $XDG_CONFIG_HOME or ~/.config on Linux.
app.setAppLogsPath()
app.userAgentFallback = `WebTorrent/${WEBTORRENT_VERSION} (https://webtorrent.io)`
app.on('open-file', onOpen)
app.on('open-url', onOpen)
ipc.init()
app.once('will-finish-launching', function () {
crashReporter.init()
})
app.on('ready', function () {
isReady = true
windows.main.init()
windows.webtorrent.init()
menu.init()
// To keep app startup fast, some code is delayed.
setTimeout(delayedInit, config.DELAYED_INIT)
// Report uncaught exceptions
process.on('uncaughtException', (err) => {
console.error(err)
var errJSON = {message: err.message, stack: err.stack}
windows.main.dispatch('uncaughtError', 'main', errJSON)
})
})
app.once('ipcReady', function () {
app.once('ipcReady', () => {
log('Command line args:', argv)
processArgv(argv)
console.timeEnd('init')
})
app.on('before-quit', function (e) {
app.on('before-quit', e => {
if (app.isQuitting) return
app.isQuitting = true
e.preventDefault()
windows.main.dispatch('saveState') // try to save state on exit
ipcMain.once('savedState', () => app.quit())
windows.main.dispatch('stateSaveImmediate') // try to save state on exit
ipcMain.once('stateSaved', () => app.quit())
setTimeout(() => {
console.error('Saving state took too long. Quitting.')
app.quit()
}, 2000) // quit after 2 secs, at most
}, 4000) // quit after 4 secs, at most
})
app.on('activate', function () {
app.on('activate', () => {
if (isReady) windows.main.show()
})
}
function delayedInit () {
function delayedInit (state) {
if (app.isQuitting) return
const announcement = require('./announcement')
const dock = require('./dock')
const updater = require('./updater')
const FolderWatcher = require('./folder-watcher')
const folderWatcher = new FolderWatcher({ window: windows.main, state })
announcement.init()
dock.init()
tray.init()
updater.init()
userTasks.init()
ipc.setModule('folderWatcher', folderWatcher)
if (folderWatcher.isEnabled()) {
folderWatcher.start()
}
if (process.platform === 'win32') {
const userTasks = require('./user-tasks')
userTasks.init()
}
if (process.platform !== 'darwin') {
const tray = require('./tray')
tray.init()
}
}
function onOpen (e, torrentId) {
@@ -124,7 +172,7 @@ function onOpen (e, torrentId) {
// Electron issue: https://github.com/atom/electron/issues/4338
setTimeout(() => windows.main.show(), 100)
processArgv([ torrentId ])
processArgv([torrentId])
} else {
argv.push(torrentId)
}
@@ -143,23 +191,46 @@ function onAppOpen (newArgv) {
}
}
// Remove leading args.
// Production: 1 arg, eg: /Applications/WebTorrent.app/Contents/MacOS/WebTorrent
// Development: 2 args, eg: electron .
// Test: 4 args, eg: electron -r .../mocks.js .
function sliceArgv (argv) {
return argv.slice(config.IS_PRODUCTION ? 1 : 2)
return argv.slice(
config.IS_PRODUCTION
? 1
: config.IS_TEST
? 4
: 2
)
}
function processArgv (argv) {
var torrentIds = []
argv.forEach(function (arg) {
if (arg === '-n') {
dialog.openSeedDirectory()
} else if (arg === '-o') {
dialog.openTorrentFile()
} else if (arg === '-u') {
dialog.openTorrentAddress()
const torrentIds = []
argv.forEach(arg => {
if (arg === '-n' || arg === '-o' || arg === '-u') {
// Critical path: Only load the 'dialog' package if it is needed
const dialog = require('./dialog')
if (arg === '-n') {
dialog.openSeedDirectory()
} else if (arg === '-o') {
dialog.openTorrentFile()
} else if (arg === '-u') {
dialog.openTorrentAddress()
}
} else if (arg === '--hidden') {
// Ignore hidden argument, already being handled
} else if (arg.startsWith('-psn')) {
// Ignore Mac launchd "process serial number" argument
// Issue: https://github.com/feross/webtorrent-desktop/issues/214
} else {
// Issue: https://github.com/webtorrent/webtorrent-desktop/issues/214
} else if (arg.startsWith('--')) {
// Ignore Spectron flags
} else if (arg === 'data:,') {
// Ignore weird Spectron argument
} else if (arg !== '.') {
// Ignore '.' argument, which gets misinterpreted as a torrent id, when a
// development copy of WebTorrent is started while a production version is
// running.
torrentIds.push(arg)
}
})

View File

@@ -1,42 +1,36 @@
module.exports = {
init
init,
setModule
}
var electron = require('electron')
const { app, ipcMain } = require('electron')
var app = electron.app
var dialog = require('./dialog')
var dock = require('./dock')
var handlers = require('./handlers')
var log = require('./log')
var menu = require('./menu')
var powerSaveBlocker = require('./power-save-blocker')
var shell = require('./shell')
var shortcuts = require('./shortcuts')
var vlc = require('./vlc')
var windows = require('./windows')
var thumbar = require('./thumbar')
const log = require('./log')
const menu = require('./menu')
const windows = require('./windows')
// Messages from the main process, to be sent once the WebTorrent process starts
var messageQueueMainToWebTorrent = []
const messageQueueMainToWebTorrent = []
// holds a ChildProcess while we're playing a video in VLC, null otherwise
var vlcProcess
// Will hold modules injected from the app that will be used on fired
// IPC events.
const modules = {}
function setModule (name, module) {
modules[name] = module
}
function init () {
var ipc = electron.ipcMain
ipc.once('ipcReady', function (e) {
ipcMain.once('ipcReady', e => {
app.ipcReady = true
app.emit('ipcReady')
})
ipc.once('ipcReadyWebTorrent', function (e) {
ipcMain.once('ipcReadyWebTorrent', e => {
app.ipcReadyWebTorrent = true
log('sending %d queued messages from the main win to the webtorrent window',
messageQueueMainToWebTorrent.length)
messageQueueMainToWebTorrent.forEach(function (message) {
messageQueueMainToWebTorrent.forEach(message => {
windows.webtorrent.send(message.name, ...message.args)
log('webtorrent: sent queued %s', message.name)
})
@@ -46,128 +40,190 @@ function init () {
* Dialog
*/
ipc.on('openTorrentFile', () => dialog.openTorrentFile())
ipc.on('openFiles', () => dialog.openFiles())
ipcMain.on('openTorrentFile', () => {
const dialog = require('./dialog')
dialog.openTorrentFile()
})
ipcMain.on('openFiles', () => {
const dialog = require('./dialog')
dialog.openFiles()
})
/**
* Dock
*/
ipc.on('setBadge', (e, ...args) => dock.setBadge(...args))
ipc.on('downloadFinished', (e, ...args) => dock.downloadFinished(...args))
ipcMain.on('setBadge', (e, ...args) => {
const dock = require('./dock')
dock.setBadge(...args)
})
ipcMain.on('downloadFinished', (e, ...args) => {
const dock = require('./dock')
dock.downloadFinished(...args)
})
/**
* Events
* Player Events
*/
ipc.on('onPlayerOpen', function () {
menu.setPlayerOpen(true)
ipcMain.on('onPlayerOpen', () => {
const powerSaveBlocker = require('./power-save-blocker')
const shortcuts = require('./shortcuts')
const thumbar = require('./thumbar')
menu.togglePlaybackControls(true)
powerSaveBlocker.enable()
shortcuts.enable()
thumbar.enable()
})
ipc.on('onPlayerClose', function () {
menu.setPlayerOpen(false)
ipcMain.on('onPlayerUpdate', (e, ...args) => {
const thumbar = require('./thumbar')
menu.onPlayerUpdate(...args)
thumbar.onPlayerUpdate(...args)
})
ipcMain.on('onPlayerClose', () => {
const powerSaveBlocker = require('./power-save-blocker')
const shortcuts = require('./shortcuts')
const thumbar = require('./thumbar')
menu.togglePlaybackControls(false)
powerSaveBlocker.disable()
shortcuts.disable()
thumbar.disable()
})
ipc.on('onPlayerPlay', function () {
ipcMain.on('onPlayerPlay', () => {
const powerSaveBlocker = require('./power-save-blocker')
const thumbar = require('./thumbar')
powerSaveBlocker.enable()
thumbar.onPlayerPlay()
})
ipc.on('onPlayerPause', function () {
ipcMain.on('onPlayerPause', () => {
const powerSaveBlocker = require('./power-save-blocker')
const thumbar = require('./thumbar')
powerSaveBlocker.disable()
thumbar.onPlayerPause()
})
/**
* Folder Watcher Events
*/
ipcMain.on('startFolderWatcher', () => {
if (!modules.folderWatcher) {
log('IPC ERR: folderWatcher module is not defined.')
return
}
modules.folderWatcher.start()
})
ipcMain.on('stopFolderWatcher', () => {
if (!modules.folderWatcher) {
log('IPC ERR: folderWatcher module is not defined.')
return
}
modules.folderWatcher.stop()
})
/**
* Shell
*/
ipc.on('openItem', (e, ...args) => shell.openItem(...args))
ipc.on('showItemInFolder', (e, ...args) => shell.showItemInFolder(...args))
ipc.on('moveItemToTrash', (e, ...args) => shell.moveItemToTrash(...args))
ipcMain.on('openPath', (e, ...args) => {
const shell = require('./shell')
shell.openPath(...args)
})
ipcMain.on('showItemInFolder', (e, ...args) => {
const shell = require('./shell')
shell.showItemInFolder(...args)
})
ipcMain.on('moveItemToTrash', (e, ...args) => {
const shell = require('./shell')
shell.moveItemToTrash(...args)
})
/**
* File handlers
*/
ipc.on('setDefaultFileHandler', (e, flag) => {
ipcMain.on('setDefaultFileHandler', (e, flag) => {
const handlers = require('./handlers')
if (flag) handlers.install()
else handlers.uninstall()
})
/**
* Auto start on login
*/
ipcMain.on('setStartup', (e, flag) => {
const startup = require('./startup')
if (flag) startup.install()
else startup.uninstall()
})
/**
* Windows: Main
*/
var main = windows.main
const main = windows.main
ipc.on('setAspectRatio', (e, ...args) => main.setAspectRatio(...args))
ipc.on('setBounds', (e, ...args) => main.setBounds(...args))
ipc.on('setProgress', (e, ...args) => main.setProgress(...args))
ipc.on('setTitle', (e, ...args) => main.setTitle(...args))
ipc.on('show', () => main.show())
ipc.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args))
ipc.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args))
ipcMain.on('setAspectRatio', (e, ...args) => main.setAspectRatio(...args))
ipcMain.on('setBounds', (e, ...args) => main.setBounds(...args))
ipcMain.on('setProgress', (e, ...args) => main.setProgress(...args))
ipcMain.on('setTitle', (e, ...args) => main.setTitle(...args))
ipcMain.on('show', () => main.show())
ipcMain.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args))
ipcMain.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args))
/**
* VLC
* TODO: Move most of this code to vlc.js
* External Media Player
*/
ipc.on('checkForVLC', function (e) {
vlc.checkForVLC(function (isInstalled) {
windows.main.send('checkForVLC', isInstalled)
ipcMain.on('checkForExternalPlayer', (e, path) => {
const externalPlayer = require('./external-player')
externalPlayer.checkInstall(path, err => {
windows.main.send('checkForExternalPlayer', !err)
})
})
ipc.on('vlcPlay', function (e, url, title) {
var args = ['--play-and-exit', '--video-on-top', '--no-video-title-show', '--quiet', `--meta-title=${title}`, url]
log('Running vlc ' + args.join(' '))
ipcMain.on('openExternalPlayer', (e, ...args) => {
const externalPlayer = require('./external-player')
const shortcuts = require('./shortcuts')
const thumbar = require('./thumbar')
vlc.spawn(args, function (err, proc) {
if (err) return windows.main.dispatch('vlcNotFound')
vlcProcess = proc
// If it works, close the modal after a second
var closeModalTimeout = setTimeout(() =>
windows.main.dispatch('exitModal'), 1000)
vlcProcess.on('close', function (code) {
clearTimeout(closeModalTimeout)
if (!vlcProcess) return // Killed
log('VLC exited with code ', code)
if (code === 0) {
windows.main.dispatch('backToList')
} else {
windows.main.dispatch('vlcNotFound')
}
vlcProcess = null
})
vlcProcess.on('error', function (e) {
log('VLC error', e)
})
})
menu.togglePlaybackControls(false)
shortcuts.disable()
thumbar.disable()
externalPlayer.spawn(...args)
})
ipc.on('vlcQuit', function () {
if (!vlcProcess) return
log('Killing VLC, pid ' + vlcProcess.pid)
vlcProcess.kill('SIGKILL') // kill -9
vlcProcess = null
ipcMain.on('quitExternalPlayer', () => {
const externalPlayer = require('./external-player')
externalPlayer.kill()
})
// Capture all events
var oldEmit = ipc.emit
ipc.emit = function (name, e, ...args) {
/**
* Message passing
*/
const oldEmit = ipcMain.emit
ipcMain.emit = (name, e, ...args) => {
// Relay messages between the main window and the WebTorrent hidden window
if (name.startsWith('wt-') && !app.isQuitting) {
if (e.sender.browserWindowOptions.title === 'webtorrent-hidden-window') {
console.dir(e.sender.getTitle())
if (e.sender.getTitle() === 'WebTorrent Hidden Window') {
// Send message to main window
windows.main.send(name, ...args)
log('webtorrent: got %s', name)
@@ -178,8 +234,8 @@ function init () {
} else {
// Queue message for webtorrent window, it hasn't finished loading yet
messageQueueMainToWebTorrent.push({
name: name,
args: args
name,
args
})
log('webtorrent: queueing %s', name)
}
@@ -187,6 +243,6 @@ function init () {
}
// Emit all other events normally
oldEmit.call(ipc, name, e, ...args)
oldEmit.call(ipcMain, name, e, ...args)
}
}

View File

@@ -8,10 +8,8 @@ module.exports.error = error
* where they can be viewed in Developer Tools.
*/
var electron = require('electron')
var windows = require('./windows')
var app = electron.app
const { app } = require('electron')
const windows = require('./windows')
function log (...args) {
if (app.ipcReady) {

View File

@@ -1,30 +1,29 @@
module.exports = {
init,
setPlayerOpen,
togglePlaybackControls,
setWindowFocus,
setAllowNav,
onPlayerUpdate,
onToggleAlwaysOnTop,
onToggleFullScreen
}
var electron = require('electron')
const { app, Menu } = require('electron')
var app = electron.app
const config = require('../config')
const windows = require('./windows')
var config = require('../config')
var dialog = require('./dialog')
var shell = require('./shell')
var windows = require('./windows')
var menu
let menu = null
function init () {
menu = electron.Menu.buildFromTemplate(getMenuTemplate())
electron.Menu.setApplicationMenu(menu)
menu = Menu.buildFromTemplate(getMenuTemplate())
Menu.setApplicationMenu(menu)
}
function setPlayerOpen (flag) {
function togglePlaybackControls (flag) {
getMenuItem('Play/Pause').enabled = flag
getMenuItem('Skip Next').enabled = flag
getMenuItem('Skip Previous').enabled = flag
getMenuItem('Increase Volume').enabled = flag
getMenuItem('Decrease Volume').enabled = flag
getMenuItem('Step Forward').enabled = flag
@@ -32,6 +31,16 @@ function setPlayerOpen (flag) {
getMenuItem('Increase Speed').enabled = flag
getMenuItem('Decrease Speed').enabled = flag
getMenuItem('Add Subtitles File...').enabled = flag
if (flag === false) {
getMenuItem('Skip Next').enabled = false
getMenuItem('Skip Previous').enabled = false
}
}
function onPlayerUpdate (hasNext, hasPrevious) {
getMenuItem('Skip Next').enabled = hasNext
getMenuItem('Skip Previous').enabled = hasPrevious
}
function setWindowFocus (flag) {
@@ -42,9 +51,12 @@ function setWindowFocus (flag) {
// Disallow opening more screens on top of the current one.
function setAllowNav (flag) {
getMenuItem('Preferences').enabled = flag
getMenuItem('Create New Torrent...').enabled = flag
var item = getMenuItem('Create New Torrent from File...')
if (item) item.enabled = flag
if (process.platform === 'darwin') {
getMenuItem('Create New Torrent...').enabled = flag
} else {
getMenuItem('Create New Torrent from Folder...').enabled = flag
getMenuItem('Create New Torrent from File...').enabled = flag
}
}
function onToggleAlwaysOnTop (flag) {
@@ -56,16 +68,15 @@ function onToggleFullScreen (flag) {
}
function getMenuItem (label) {
for (var i = 0; i < menu.items.length; i++) {
var menuItem = menu.items[i].submenu.items.find(function (item) {
return item.label === label
})
if (menuItem) return menuItem
for (const menuItem of menu.items) {
const submenuItem = menuItem.submenu.items.find(item => item.label === label)
if (submenuItem) return submenuItem
}
return {}
}
function getMenuTemplate () {
var template = [
const template = [
{
label: 'File',
submenu: [
@@ -74,17 +85,26 @@ function getMenuTemplate () {
? 'Create New Torrent...'
: 'Create New Torrent from Folder...',
accelerator: 'CmdOrCtrl+N',
click: () => dialog.openSeedDirectory()
click: () => {
const dialog = require('./dialog')
dialog.openSeedDirectory()
}
},
{
label: 'Open Torrent File...',
accelerator: 'CmdOrCtrl+O',
click: () => dialog.openTorrentFile()
click: () => {
const dialog = require('./dialog')
dialog.openTorrentFile()
}
},
{
label: 'Open Torrent Address...',
accelerator: 'CmdOrCtrl+U',
click: () => dialog.openTorrentAddress()
click: () => {
const dialog = require('./dialog')
dialog.openTorrentAddress()
}
},
{
type: 'separator'
@@ -184,6 +204,21 @@ function getMenuTemplate () {
{
type: 'separator'
},
{
label: 'Skip Next',
accelerator: 'N',
click: () => windows.main.dispatch('nextTrack'),
enabled: false
},
{
label: 'Skip Previous',
accelerator: 'P',
click: () => windows.main.dispatch('previousTrack'),
enabled: false
},
{
type: 'separator'
},
{
label: 'Increase Volume',
accelerator: 'CmdOrCtrl+Up',
@@ -240,31 +275,75 @@ function getMenuTemplate () {
}
]
},
{
label: 'Transfers',
submenu: [
{
label: 'Pause All',
click: () => windows.main.dispatch('pauseAllTorrents')
},
{
label: 'Resume All',
click: () => windows.main.dispatch('resumeAllTorrents')
},
{
label: 'Remove All From List',
click: () => windows.main.dispatch('confirmDeleteAllTorrents', false)
},
{
label: 'Remove All Data Files',
click: () => windows.main.dispatch('confirmDeleteAllTorrents', true)
}
]
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'Learn more about ' + config.APP_NAME,
click: () => shell.openExternal(config.HOME_PAGE_URL)
click: () => {
const shell = require('./shell')
shell.openExternal(config.HOME_PAGE_URL)
}
},
{
label: 'Release Notes',
click: () => {
const shell = require('./shell')
shell.openExternal(config.GITHUB_URL_RELEASES)
}
},
{
label: 'Contribute on GitHub',
click: () => shell.openExternal(config.GITHUB_URL)
click: () => {
const shell = require('./shell')
shell.openExternal(config.GITHUB_URL)
}
},
{
type: 'separator'
},
{
label: 'Report an Issue...',
click: () => shell.openExternal(config.GITHUB_URL_ISSUES)
click: () => {
const shell = require('./shell')
shell.openExternal(config.GITHUB_URL_ISSUES)
}
},
{
label: 'Follow us on Twitter',
click: () => {
const shell = require('./shell')
shell.openExternal(config.TWITTER_PAGE_URL)
}
}
]
}
]
if (process.platform === 'darwin') {
// Add WebTorrent app menu (Mac)
// WebTorrent menu (Mac)
template.unshift({
label: config.APP_NAME,
submenu: [
@@ -283,8 +362,7 @@ function getMenuTemplate () {
type: 'separator'
},
{
role: 'services',
submenu: []
role: 'services'
},
{
type: 'separator'
@@ -307,8 +385,26 @@ function getMenuTemplate () {
]
})
// Add Window menu (Mac)
template.splice(5, 0, {
// Edit menu (Mac)
template[2].submenu.push(
{
type: 'separator'
},
{
label: 'Speech',
submenu: [
{
role: 'startspeaking'
},
{
role: 'stopspeaking'
}
]
}
)
// Window menu (Mac)
template.splice(6, 0, {
role: 'window',
submenu: [
{
@@ -330,7 +426,10 @@ function getMenuTemplate () {
// File menu (Windows, Linux)
template[0].submenu.unshift({
label: 'Create New Torrent from File...',
click: () => dialog.openSeedFile()
click: () => {
const dialog = require('./dialog')
dialog.openSeedFile()
}
})
// Edit menu (Windows, Linux)
@@ -345,7 +444,7 @@ function getMenuTemplate () {
})
// Help menu (Windows, Linux)
template[4].submenu.push(
template[5].submenu.push(
{
type: 'separator'
},

View File

@@ -3,18 +3,21 @@ module.exports = {
disable
}
var electron = require('electron')
var log = require('./log')
const { powerSaveBlocker } = require('electron')
const log = require('./log')
var blockId = 0
let blockId = 0
/**
* Block the system from entering low-power (sleep) mode or turning off the
* display.
*/
function enable () {
disable() // Stop the previous power saver block, if one exists.
blockId = electron.powerSaveBlocker.start('prevent-display-sleep')
if (powerSaveBlocker.isStarted(blockId)) {
// If a power saver block already exists, do nothing.
return
}
blockId = powerSaveBlocker.start('prevent-display-sleep')
log(`powerSaveBlocker.enable: ${blockId}`)
}
@@ -22,9 +25,10 @@ function enable () {
* Stop blocking the system from entering low-power mode.
*/
function disable () {
if (!electron.powerSaveBlocker.isStarted(blockId)) {
if (!powerSaveBlocker.isStarted(blockId)) {
// If a power saver block does not exist, do nothing.
return
}
electron.powerSaveBlocker.stop(blockId)
powerSaveBlocker.stop(blockId)
log(`powerSaveBlocker.disable: ${blockId}`)
}

View File

@@ -1,27 +1,28 @@
module.exports = {
openExternal,
openItem,
openPath,
showItemInFolder,
moveItemToTrash
}
var electron = require('electron')
var log = require('./log')
const { shell } = require('electron')
const log = require('./log')
/**
* Open the given external protocol URL in the desktops default manner.
*/
function openExternal (url) {
log(`openExternal: ${url}`)
electron.shell.openExternal(url)
shell.openExternal(url)
}
/**
* Open the given file in the desktops default manner.
*/
function openItem (path) {
log(`openItem: ${path}`)
electron.shell.openItem(path)
function openPath (path) {
log(`openPath: ${path}`)
shell.openPath(path)
}
/**
@@ -29,7 +30,7 @@ function openItem (path) {
*/
function showItemInFolder (path) {
log(`showItemInFolder: ${path}`)
electron.shell.showItemInFolder(path)
shell.showItemInFolder(path)
}
/**
@@ -37,5 +38,5 @@ function showItemInFolder (path) {
*/
function moveItemToTrash (path) {
log(`moveItemToTrash: ${path}`)
electron.shell.moveItemToTrash(path)
shell.trashItem(path)
}

View File

@@ -3,18 +3,28 @@ module.exports = {
enable
}
var electron = require('electron')
var windows = require('./windows')
const { globalShortcut } = require('electron')
const windows = require('./windows')
function enable () {
// Register play/pause media key, available on some keyboards.
electron.globalShortcut.register(
globalShortcut.register(
'MediaPlayPause',
() => windows.main.dispatch('playPause')
)
globalShortcut.register(
'MediaNextTrack',
() => windows.main.dispatch('nextTrack')
)
globalShortcut.register(
'MediaPreviousTrack',
() => windows.main.dispatch('previousTrack')
)
}
function disable () {
// Return the media key to the OS, so other apps can use it.
electron.globalShortcut.unregister('MediaPlayPause')
globalShortcut.unregister('MediaPlayPause')
globalShortcut.unregister('MediaNextTrack')
globalShortcut.unregister('MediaPreviousTrack')
}

View File

@@ -2,150 +2,39 @@ module.exports = {
handleEvent
}
var cp = require('child_process')
var electron = require('electron')
var fs = require('fs')
var os = require('os')
var path = require('path')
const { app } = require('electron')
var app = electron.app
const path = require('path')
const spawn = require('child_process').spawn
var handlers = require('./handlers')
const handlers = require('./handlers')
var EXE_NAME = path.basename(process.execPath)
var UPDATE_EXE = path.join(process.execPath, '..', '..', 'Update.exe')
const EXE_NAME = path.basename(process.execPath)
const UPDATE_EXE = path.join(process.execPath, '..', '..', 'Update.exe')
const run = (args, done) => {
spawn(UPDATE_EXE, args, { detached: true })
.on('close', done)
}
function handleEvent (cmd) {
if (cmd === '--squirrel-install') {
// App was installed. Install desktop/start menu shortcuts.
createShortcuts(function () {
// Ensure user sees install splash screen so they realize that Setup.exe actually
// installed an application and isn't the application itself.
setTimeout(function () {
app.quit()
}, 3000)
})
return true
}
if (cmd === '--squirrel-updated') {
// App was updated. (Called on new version of app)
updateShortcuts(function () {
app.quit()
})
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
run([`--createShortcut=${EXE_NAME}`], app.quit)
return true
}
if (cmd === '--squirrel-uninstall') {
// App was just uninstalled. Undo anything we did in the --squirrel-install and
// --squirrel-updated handlers
// Uninstall .torrent file and magnet link handlers
handlers.uninstall()
// Remove desktop/start menu shortcuts.
// HACK: add a callback to handlers.uninstall() so we can remove this setTimeout
setTimeout(function () {
removeShortcuts(function () {
app.quit()
})
}, 1000)
run([`--removeShortcut=${EXE_NAME}`], app.quit)
return true
}
if (cmd === '--squirrel-obsolete') {
// App will be updated. (Called on outgoing version of app)
app.quit()
return true
}
if (cmd === '--squirrel-firstrun') {
// App is running for the first time. Do not quit, allow startup to continue.
return false
}
return false
}
/**
* Spawn a command and invoke the callback when it completes with an error and
* the output from standard out.
*/
function spawn (command, args, cb) {
var stdout = ''
var child
try {
child = cp.spawn(command, args)
} catch (err) {
// Spawn can throw an error
process.nextTick(function () {
cb(error, stdout)
})
return
}
child.stdout.on('data', function (data) {
stdout += data
})
var error = null
child.on('error', function (processError) {
error = processError
})
child.on('close', function (code, signal) {
if (code !== 0 && !error) error = new Error('Command failed: #{signal || code}')
if (error) error.stdout = stdout
cb(error, stdout)
})
}
/**
* Spawn the Squirrel `Update.exe` command with the given arguments and invoke
* the callback when the command completes.
*/
function spawnUpdate (args, cb) {
spawn(UPDATE_EXE, args, cb)
}
/**
* Create desktop and start menu shortcuts using the Squirrel `Update.exe`
* command.
*/
function createShortcuts (cb) {
spawnUpdate(['--createShortcut', EXE_NAME], cb)
}
/**
* Update desktop and start menu shortcuts using the Squirrel `Update.exe`
* command.
*/
function updateShortcuts (cb) {
var homeDir = os.homedir()
if (homeDir) {
var desktopShortcutPath = path.join(homeDir, 'Desktop', 'WebTorrent.lnk')
// If the desktop shortcut was deleted by the user, then keep it deleted.
fs.access(desktopShortcutPath, function (err) {
var desktopShortcutExists = !err
createShortcuts(function () {
if (desktopShortcutExists) {
cb()
} else {
// Remove the unwanted desktop shortcut that was recreated
fs.unlink(desktopShortcutPath, cb)
}
})
})
} else {
createShortcuts(cb)
}
}
/**
* Remove desktop and start menu shortcuts using the Squirrel `Update.exe`
* command.
*/
function removeShortcuts (cb) {
spawnUpdate(['--removeShortcut', EXE_NAME], cb)
}

28
src/main/startup.js Normal file
View File

@@ -0,0 +1,28 @@
module.exports = {
install,
uninstall
}
const { APP_NAME } = require('../config')
const AutoLaunch = require('auto-launch')
const appLauncher = new AutoLaunch({
name: APP_NAME,
isHidden: true
})
function install () {
return appLauncher
.isEnabled()
.then(enabled => {
if (!enabled) return appLauncher.enable()
})
}
function uninstall () {
return appLauncher
.isEnabled()
.then(enabled => {
if (enabled) return appLauncher.disable()
})
}

View File

@@ -2,7 +2,8 @@ module.exports = {
disable,
enable,
onPlayerPause,
onPlayerPlay
onPlayerPlay,
onPlayerUpdate
}
/**
@@ -11,44 +12,80 @@ module.exports = {
* or activating the window.
*/
var path = require('path')
var config = require('../config')
const path = require('path')
const config = require('../config')
var windows = require('./windows')
const windows = require('./windows')
const PREV_ICON = path.join(config.STATIC_PATH, 'PreviousTrackThumbnailBarButton.png')
const PLAY_ICON = path.join(config.STATIC_PATH, 'PlayThumbnailBarButton.png')
const PAUSE_ICON = path.join(config.STATIC_PATH, 'PauseThumbnailBarButton.png')
const NEXT_ICON = path.join(config.STATIC_PATH, 'NextTrackThumbnailBarButton.png')
// Array indices for each button
const PREV = 0
const PLAY_PAUSE = 1
const NEXT = 2
let buttons = []
/**
* Show the Windows thumbnail toolbar buttons.
*/
function enable () {
update(false)
buttons = [
{
tooltip: 'Previous Track',
icon: PREV_ICON,
click: () => windows.main.dispatch('previousTrack')
},
{
tooltip: 'Pause',
icon: PAUSE_ICON,
click: () => windows.main.dispatch('playPause')
},
{
tooltip: 'Next Track',
icon: NEXT_ICON,
click: () => windows.main.dispatch('nextTrack')
}
]
update()
}
/**
* Hide the Windows thumbnail toolbar buttons.
*/
function disable () {
windows.main.win.setThumbarButtons([])
buttons = []
update()
}
function onPlayerPause () {
update(true)
if (!isEnabled()) return
buttons[PLAY_PAUSE].tooltip = 'Play'
buttons[PLAY_PAUSE].icon = PLAY_ICON
update()
}
function onPlayerPlay () {
update(false)
if (!isEnabled()) return
buttons[PLAY_PAUSE].tooltip = 'Pause'
buttons[PLAY_PAUSE].icon = PAUSE_ICON
update()
}
function update (isPaused) {
var icon = isPaused
? 'PlayThumbnailBarButton.png'
: 'PauseThumbnailBarButton.png'
function onPlayerUpdate (state) {
if (!isEnabled()) return
buttons[PREV].flags = [state.hasPrevious ? 'enabled' : 'disabled']
buttons[NEXT].flags = [state.hasNext ? 'enabled' : 'disabled']
update()
}
var buttons = [
{
tooltip: isPaused ? 'Play' : 'Pause',
icon: path.join(config.STATIC_PATH, icon),
click: () => windows.main.dispatch('playPause')
}
]
function isEnabled () {
return buttons.length > 0
}
function update () {
windows.main.win.setThumbarButtons(buttons)
}

View File

@@ -4,14 +4,12 @@ module.exports = {
setWindowFocus
}
var electron = require('electron')
const { app, Tray, Menu } = require('electron')
var app = electron.app
const config = require('../config')
const windows = require('./windows')
var config = require('../config')
var windows = require('./windows')
var tray
let tray
function init () {
if (process.platform === 'linux') {
@@ -36,8 +34,8 @@ function setWindowFocus (flag) {
}
function initLinux () {
checkLinuxTraySupport(function (supportsTray) {
if (supportsTray) createTray()
checkLinuxTraySupport(err => {
if (!err) createTray()
})
}
@@ -46,24 +44,20 @@ function initWin32 () {
}
/**
* Check for libappindicator1 support before creating tray icon
* Check for libappindicator support before creating tray icon.
*/
function checkLinuxTraySupport (cb) {
var cp = require('child_process')
const cp = require('child_process')
// Check that we're on Ubuntu (or another debian system) and that we have
// libappindicator1. If WebTorrent was installed from the deb file, we should
// always have it. If it was installed from the zip file, we might not.
cp.exec('dpkg --get-selections libappindicator1', function (err, stdout) {
if (err) return cb(false)
// Unfortunately there's no cleaner way, as far as I can tell, to check
// whether a debian package is installed:
cb(stdout.endsWith('\tinstall\n'))
// Check that libappindicator libraries are installed in system.
cp.exec('ldconfig -p | grep libappindicator', (err, stdout) => {
if (err) return cb(err)
cb(null)
})
}
function createTray () {
tray = new electron.Tray(getIconPath())
tray = new Tray(getIconPath())
// On Windows, left click opens the app, right click opens the context menu.
// On Linux, any click (left or right) opens the context menu.
@@ -74,7 +68,7 @@ function createTray () {
}
function updateTrayMenu () {
var contextMenu = electron.Menu.buildFromTemplate(getMenuTemplate())
const contextMenu = Menu.buildFromTemplate(getMenuTemplate())
tray.setContextMenu(contextMenu)
}

View File

@@ -2,16 +2,17 @@ module.exports = {
init
}
var electron = require('electron')
var get = require('simple-get')
const { autoUpdater } = require('electron')
const get = require('simple-get')
var config = require('../config')
var log = require('./log')
var windows = require('./windows')
const config = require('../config')
const log = require('./log')
const windows = require('./windows')
var AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
const AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
'?version=' + config.APP_VERSION +
'&platform=' + process.platform
'&platform=' + process.platform +
'&sysarch=' + config.OS_SYSARCH
function init () {
if (process.platform === 'linux') {
@@ -46,31 +47,31 @@ function onResponse (err, res, data) {
}
function initDarwinWin32 () {
electron.autoUpdater.on(
autoUpdater.on(
'error',
(err) => log.error(`Update error: ${err.message}`)
)
electron.autoUpdater.on(
autoUpdater.on(
'checking-for-update',
() => log('Checking for update')
)
electron.autoUpdater.on(
autoUpdater.on(
'update-available',
() => log('Update available')
)
electron.autoUpdater.on(
autoUpdater.on(
'update-not-available',
() => log('No update available')
)
electron.autoUpdater.on(
autoUpdater.on(
'update-downloaded',
(e, notes, name, date, url) => log(`Update downloaded: ${name}: ${url}`)
)
electron.autoUpdater.setFeedURL(AUTO_UPDATE_URL)
electron.autoUpdater.checkForUpdates()
autoUpdater.setFeedURL({ url: AUTO_UPDATE_URL })
autoUpdater.checkForUpdates()
}

View File

@@ -2,9 +2,7 @@ module.exports = {
init
}
var electron = require('electron')
var app = electron.app
const { app } = require('electron')
/**
* Add a user task menu to the app icon on right-click. (Windows)

View File

@@ -1,22 +0,0 @@
module.exports = {
checkForVLC,
spawn
}
var cp = require('child_process')
var vlcCommand = require('vlc-command')
// Finds if VLC is installed on Mac, Windows, or Linux.
// Calls back with true or false: whether VLC was detected
function checkForVLC (cb) {
vlcCommand((err) => cb(!err))
}
// Spawns VLC with child_process.spawn() to return a ChildProcess object
// Calls back with (err, childProcess)
function spawn (args, cb) {
vlcCommand(function (err, vlcPath) {
if (err) return cb(err)
cb(null, cp.spawn(vlcPath, args))
})
}

View File

@@ -1,21 +1,21 @@
var about = module.exports = {
const about = module.exports = {
init,
win: null
}
var config = require('../../config')
var electron = require('electron')
const config = require('../../config')
const { BrowserWindow } = require('electron')
function init () {
if (about.win) {
return about.win.show()
}
var win = about.win = new electron.BrowserWindow({
const win = about.win = new BrowserWindow({
backgroundColor: '#ECECEC',
center: true,
fullscreen: false,
height: 170,
height: 250,
icon: getIconPath(),
maximizable: false,
minimizable: false,
@@ -24,19 +24,28 @@ function init () {
skipTaskbar: true,
title: 'About ' + config.APP_WINDOW_TITLE,
useContentSize: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableBlinkFeatures: 'AudioVideoTracks',
enableRemoteModule: true,
backgroundThrottling: false
},
width: 300
})
require('@electron/remote/main').enable(win.webContents)
win.loadURL(config.WINDOW_ABOUT)
// No menu on the About window
win.setMenu(null)
win.webContents.once('did-finish-load', function () {
win.once('ready-to-show', () => {
win.show()
// No menu on the About window
// Hack: BrowserWindow removeMenu method not working on electron@7
// https://github.com/electron/electron/issues/21088
win.setMenuBarVisibility(false)
})
win.once('closed', function () {
win.once('closed', () => {
about.win = null
})
}

View File

@@ -1,4 +1,4 @@
var main = module.exports = {
const main = module.exports = {
dispatch,
hide,
init,
@@ -14,65 +14,99 @@ var main = module.exports = {
win: null
}
var electron = require('electron')
const { app, BrowserWindow, screen } = require('electron')
const debounce = require('debounce')
var app = electron.app
const config = require('../../config')
const log = require('../log')
const menu = require('../menu')
var config = require('../../config')
var log = require('../log')
var menu = require('../menu')
var tray = require('../tray')
var HEADER_HEIGHT = 37
var TORRENT_HEIGHT = 100
function init () {
function init (state, options) {
if (main.win) {
return main.win.show()
}
var win = main.win = new electron.BrowserWindow({
backgroundColor: '#1E1E1E',
const initialBounds = Object.assign(config.WINDOW_INITIAL_BOUNDS, state.saved.bounds)
const win = main.win = new BrowserWindow({
backgroundColor: '#282828',
darkTheme: true, // Forces dark theme (GTK+3)
height: initialBounds.height,
icon: getIconPath(), // Window icon (Windows, Linux)
minWidth: config.WINDOW_MIN_WIDTH,
minHeight: config.WINDOW_MIN_HEIGHT,
minWidth: config.WINDOW_MIN_WIDTH,
show: false,
title: config.APP_WINDOW_TITLE,
titleBarStyle: 'hidden-inset', // Hide title bar (Mac)
titleBarStyle: 'hiddenInset', // Hide title bar (Mac)
useContentSize: true, // Specify web page size without OS chrome
width: 500,
height: HEADER_HEIGHT + (TORRENT_HEIGHT * 6) // header height + 5 torrents
width: initialBounds.width,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableBlinkFeatures: 'AudioVideoTracks',
enableRemoteModule: true,
backgroundThrottling: false
},
x: initialBounds.x,
y: initialBounds.y
})
require('@electron/remote/main').enable(win.webContents)
win.loadURL(config.WINDOW_MAIN)
if (win.setSheetOffset) win.setSheetOffset(HEADER_HEIGHT)
win.once('ready-to-show', () => {
if (!options.hidden) win.show()
})
win.webContents.on('dom-ready', function () {
if (win.setSheetOffset) {
win.setSheetOffset(config.UI_HEADER_HEIGHT)
}
win.webContents.on('dom-ready', () => {
menu.onToggleFullScreen(main.win.isFullScreen())
})
win.webContents.on('will-navigate', (e) => {
// Prevent drag-and-drop from navigating the Electron window, which can happen
// before our drag-and-drop handlers have been initialized.
e.preventDefault()
})
win.on('blur', onWindowBlur)
win.on('focus', onWindowFocus)
win.on('hide', onWindowBlur)
win.on('show', onWindowFocus)
win.on('enter-full-screen', function () {
win.on('enter-full-screen', () => {
menu.onToggleFullScreen(true)
send('fullscreenChanged', true)
win.setMenuBarVisibility(false)
})
win.on('leave-full-screen', function () {
win.on('leave-full-screen', () => {
menu.onToggleFullScreen(false)
send('fullscreenChanged', false)
win.setMenuBarVisibility(true)
})
win.on('close', function (e) {
if (process.platform !== 'darwin' && !tray.hasTray()) {
app.quit()
} else if (!app.isQuitting) {
win.on('move', debounce(e => {
send('windowBoundsChanged', main.win.getBounds())
}, 1000))
win.on('resize', debounce(e => {
send('windowBoundsChanged', main.win.getBounds())
}, 1000))
win.on('close', e => {
if (process.platform !== 'darwin') {
const tray = require('../tray')
if (!tray.hasTray()) {
app.quit()
return
}
}
if (!app.isQuitting) {
e.preventDefault()
hide()
}
@@ -85,7 +119,7 @@ function dispatch (...args) {
function hide () {
if (!main.win) return
main.win.send('dispatch', 'backToList')
dispatch('backToList')
main.win.hide()
}
@@ -109,41 +143,37 @@ function setAspectRatio (aspectRatio) {
function setBounds (bounds, maximize) {
// Do nothing in fullscreen
if (!main.win || main.win.isFullScreen()) {
log('setBounds: not setting bounds because we\'re in full screen')
log('setBounds: not setting bounds because already in full screen mode')
return
}
// Maximize or minimize, if the second argument is present
var willBeMaximized
if (maximize === true) {
if (!main.win.isMaximized()) {
log('setBounds: maximizing')
main.win.maximize()
}
willBeMaximized = true
} else if (maximize === false) {
if (main.win.isMaximized()) {
log('setBounds: unmaximizing')
main.win.unmaximize()
}
willBeMaximized = false
} else {
willBeMaximized = main.win.isMaximized()
if (maximize === true && !main.win.isMaximized()) {
log('setBounds: maximizing')
main.win.maximize()
} else if (maximize === false && main.win.isMaximized()) {
log('setBounds: minimizing')
main.win.unmaximize()
}
const willBeMaximized = typeof maximize === 'boolean' ? maximize : main.win.isMaximized()
// Assuming we're not maximized or maximizing, set the window size
if (!willBeMaximized) {
log('setBounds: setting bounds to ' + JSON.stringify(bounds))
log(`setBounds: setting bounds to ${JSON.stringify(bounds)}`)
if (bounds.x === null && bounds.y === null) {
// X and Y not specified? By default, center on current screen
var scr = electron.screen.getDisplayMatching(main.win.getBounds())
bounds.x = Math.round(scr.bounds.x + scr.bounds.width / 2 - bounds.width / 2)
bounds.y = Math.round(scr.bounds.y + scr.bounds.height / 2 - bounds.height / 2)
log('setBounds: centered to ' + JSON.stringify(bounds))
const scr = screen.getDisplayMatching(main.win.getBounds())
bounds.x = Math.round(scr.bounds.x + (scr.bounds.width / 2) - (bounds.width / 2))
bounds.y = Math.round(scr.bounds.y + (scr.bounds.height / 2) - (bounds.height / 2))
log(`setBounds: centered to ${JSON.stringify(bounds)}`)
}
// Resize the window's content area (so window border doesn't need to be taken
// into account)
main.win.setContentBounds(bounds, true)
if (bounds.contentBounds) {
main.win.setContentBounds(bounds, true)
} else {
main.win.setBounds(bounds, true)
}
} else {
log('setBounds: not setting bounds because of window maximization')
}
@@ -184,7 +214,7 @@ function toggleDevTools () {
if (main.win.webContents.isDevToolsOpened()) {
main.win.webContents.closeDevTools()
} else {
main.win.webContents.openDevTools({ detach: true })
main.win.webContents.openDevTools({ mode: 'detach' })
}
}
@@ -207,12 +237,20 @@ function toggleFullScreen (flag) {
function onWindowBlur () {
menu.setWindowFocus(false)
tray.setWindowFocus(false)
if (process.platform !== 'darwin') {
const tray = require('../tray')
tray.setWindowFocus(false)
}
}
function onWindowFocus () {
menu.setWindowFocus(true)
tray.setWindowFocus(true)
if (process.platform !== 'darwin') {
const tray = require('../tray')
tray.setWindowFocus(true)
}
}
function getIconPath () {

View File

@@ -1,4 +1,4 @@
var webtorrent = module.exports = {
const webtorrent = module.exports = {
init,
send,
show,
@@ -6,13 +6,12 @@ var webtorrent = module.exports = {
win: null
}
var electron = require('electron')
const { app, BrowserWindow } = require('electron')
var config = require('../../config')
var log = require('../log')
const config = require('../../config')
function init () {
var win = webtorrent.win = new electron.BrowserWindow({
const win = webtorrent.win = new BrowserWindow({
backgroundColor: '#1E1E1E',
center: true,
fullscreen: false,
@@ -25,14 +24,22 @@ function init () {
skipTaskbar: true,
title: 'webtorrent-hidden-window',
useContentSize: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableBlinkFeatures: 'AudioVideoTracks',
enableRemoteModule: true,
backgroundThrottling: false
},
width: 150
})
require('@electron/remote/main').enable(win.webContents)
win.loadURL(config.WINDOW_WEBTORRENT)
// Prevent killing the WebTorrent process
win.on('close', function (e) {
if (electron.app.isQuitting) {
win.on('close', e => {
if (app.isQuitting) {
return
}
e.preventDefault()
@@ -52,11 +59,10 @@ function send (...args) {
function toggleDevTools () {
if (!webtorrent.win) return
log('toggleDevTools')
if (webtorrent.win.webContents.isDevToolsOpened()) {
webtorrent.win.webContents.closeDevTools()
webtorrent.win.hide()
} else {
webtorrent.win.webContents.openDevTools({ detach: true })
webtorrent.win.webContents.openDevTools({ mode: 'detach' })
}
}

View File

@@ -1,6 +1,6 @@
const React = require('react')
const {dispatcher} = require('../lib/dispatcher')
const { dispatcher } = require('../lib/dispatcher')
module.exports = class CreateTorrentErrorPage extends React.Component {
render () {

View File

@@ -0,0 +1,31 @@
const React = require('react')
const ModalOKCancel = require('./modal-ok-cancel')
const { dispatch, dispatcher } = require('../lib/dispatcher')
module.exports = class DeleteAllTorrentsModal extends React.Component {
render () {
const { state: { modal: { deleteData } } } = this.props
const message = deleteData
? 'Are you sure you want to remove all the torrents from the list and delete the data files?'
: 'Are you sure you want to remove all the torrents from the list?'
const buttonText = deleteData ? 'REMOVE DATA' : 'REMOVE'
return (
<div>
<p><strong>{message}</strong></p>
<ModalOKCancel
cancelText='CANCEL'
onCancel={dispatcher('exitModal')}
okText={buttonText}
onOK={handleRemove}
/>
</div>
)
function handleRemove () {
dispatch('deleteAllTorrents', deleteData)
dispatch('exitModal')
}
}
}

View File

@@ -1,24 +1,38 @@
const React = require('react')
const {dispatcher} = require('../lib/dispatcher')
const { dispatcher } = require('../lib/dispatcher')
module.exports = class Header extends React.Component {
class Header extends React.Component {
render () {
var loc = this.props.state.location
const loc = this.props.state.location
return (
<div key='header' className='header'>
<div
className='header'
onMouseMove={dispatcher('mediaMouseMoved')}
onMouseEnter={dispatcher('mediaControlsMouseEnter')}
onMouseLeave={dispatcher('mediaControlsMouseLeave')}
role='navigation'
>
{this.getTitle()}
<div className='nav left float-left'>
<i
className={'icon back ' + (loc.hasBack() ? '' : 'disabled')}
title='Back'
onClick={dispatcher('back')}>
onClick={dispatcher('back')}
role='button'
aria-disabled={!loc.hasBack()}
aria-label='Back'
>
chevron_left
</i>
<i
className={'icon forward ' + (loc.hasForward() ? '' : 'disabled')}
title='Forward'
onClick={dispatcher('forward')}>
onClick={dispatcher('forward')}
role='button'
aria-disabled={!loc.hasForward()}
aria-label='Forward'
>
chevron_right
</i>
</div>
@@ -31,20 +45,24 @@ module.exports = class Header extends React.Component {
getTitle () {
if (process.platform !== 'darwin') return null
var state = this.props.state
const state = this.props.state
return (<div className='title ellipsis'>{state.window.title}</div>)
}
getAddButton () {
var state = this.props.state
const state = this.props.state
if (state.location.url() !== 'home') return null
return (
<i
className='icon add'
title='Add torrent'
onClick={dispatcher('openFiles')}>
onClick={dispatcher('openFiles')}
role='button'
>
add
</i>
)
}
}
module.exports = Header

View File

@@ -0,0 +1,35 @@
const React = require('react')
const PropTypes = require('prop-types')
const colors = require('material-ui/styles/colors')
class Heading extends React.Component {
static get propTypes () {
return {
level: PropTypes.number
}
}
static get defaultProps () {
return {
level: 1
}
}
render () {
const HeadingTag = 'h' + this.props.level
const style = {
color: colors.grey100,
fontSize: 20,
marginBottom: 15,
marginTop: 30
}
return (
<HeadingTag style={style}>
{this.props.children}
</HeadingTag>
)
}
}
module.exports = Heading

View File

@@ -0,0 +1,27 @@
const React = require('react')
const FlatButton = require('material-ui/FlatButton').default
const RaisedButton = require('material-ui/RaisedButton').default
module.exports = class ModalOKCancel extends React.Component {
render () {
const cancelStyle = { marginRight: 10, color: 'black' }
const { cancelText, onCancel, okText, onOK } = this.props
return (
<div className='float-right'>
<FlatButton
className='control cancel'
style={cancelStyle}
label={cancelText}
onClick={onCancel}
/>
<RaisedButton
className='control ok'
primary
label={okText}
onClick={onOK}
autoFocus
/>
</div>
)
}
}

View File

@@ -0,0 +1,51 @@
const React = require('react')
const TextField = require('material-ui/TextField').default
const { clipboard } = require('electron')
const ModalOKCancel = require('./modal-ok-cancel')
const { dispatch, dispatcher } = require('../lib/dispatcher')
const { isMagnetLink } = require('../lib/torrent-player')
module.exports = class OpenTorrentAddressModal extends React.Component {
render () {
return (
<div className='open-torrent-address-modal'>
<p><label>Enter torrent address or magnet link</label></p>
<div>
<TextField
id='torrent-address-field'
className='control'
ref={(c) => { this.torrentURL = c }}
fullWidth
onKeyDown={handleKeyDown.bind(this)}
/>
</div>
<ModalOKCancel
cancelText='CANCEL'
onCancel={dispatcher('exitModal')}
okText='OK'
onOK={handleOK.bind(this)}
/>
</div>
)
}
componentDidMount () {
this.torrentURL.input.focus()
const clipboardContent = clipboard.readText()
if (isMagnetLink(clipboardContent)) {
this.torrentURL.input.value = clipboardContent
this.torrentURL.input.select()
}
}
}
function handleKeyDown (e) {
if (e.which === 13) handleOK.call(this) /* hit Enter to submit */
}
function handleOK () {
dispatch('exitModal')
dispatch('addTorrent', this.torrentURL.input.value)
}

View File

@@ -0,0 +1,85 @@
const path = require('path')
const colors = require('material-ui/styles/colors')
const remote = require('@electron/remote')
const React = require('react')
const PropTypes = require('prop-types')
const RaisedButton = require('material-ui/RaisedButton').default
const TextField = require('material-ui/TextField').default
// Lets you pick a file or directory.
// Uses the system Open File dialog.
// You can't edit the text field directly.
class PathSelector extends React.Component {
static propTypes () {
return {
className: PropTypes.string,
dialog: PropTypes.object,
id: PropTypes.string,
onChange: PropTypes.func,
title: PropTypes.string.isRequired,
value: PropTypes.string
}
}
constructor (props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick () {
const opts = Object.assign({
defaultPath: path.dirname(this.props.value || ''),
properties: ['openFile', 'openDirectory']
}, this.props.dialog)
const filenames = remote.dialog.showOpenDialogSync(remote.getCurrentWindow(), opts)
if (!Array.isArray(filenames)) return
this.props.onChange && this.props.onChange(filenames[0])
}
render () {
const id = this.props.title.replace(' ', '-').toLowerCase()
const wrapperStyle = {
alignItems: 'center',
display: 'flex',
width: '100%'
}
const labelStyle = {
flex: '0 auto',
marginRight: 10,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
const textareaStyle = {
color: colors.grey50
}
const textFieldStyle = {
flex: '1'
}
const text = this.props.value || ''
const buttonStyle = {
marginLeft: 10
}
return (
<div className={this.props.className} style={wrapperStyle}>
<div className='label' style={labelStyle}>
{this.props.title}:
</div>
<TextField
className='control' disabled id={id} value={text}
inputStyle={textareaStyle} style={textFieldStyle}
/>
<RaisedButton
className='control' label='Change' onClick={this.handleClick}
style={buttonStyle}
/>
</div>
)
}
}
module.exports = PathSelector

View File

@@ -1,22 +1,25 @@
const React = require('react')
const {dispatch, dispatcher} = require('../lib/dispatcher')
const ModalOKCancel = require('./modal-ok-cancel')
const { dispatch, dispatcher } = require('../lib/dispatcher')
module.exports = class RemoveTorrentModal extends React.Component {
render () {
var state = this.props.state
var message = state.modal.deleteData
const state = this.props.state
const message = state.modal.deleteData
? 'Are you sure you want to remove this torrent from the list and delete the data file?'
: 'Are you sure you want to remove this torrent from the list?'
var buttonText = state.modal.deleteData ? 'Remove Data' : 'Remove'
const buttonText = state.modal.deleteData ? 'REMOVE DATA' : 'REMOVE'
return (
<div>
<p><strong>{message}</strong></p>
<p className='float-right'>
<button className='button button-flat' onClick={dispatcher('exitModal')}>Cancel</button>
<button className='button button-raised' onClick={handleRemove}>{buttonText}</button>
</p>
<ModalOKCancel
cancelText='CANCEL'
onCancel={dispatcher('exitModal')}
okText={buttonText}
onOK={handleRemove}
/>
</div>
)

View File

@@ -0,0 +1,55 @@
const React = require('react')
const PropTypes = require('prop-types')
const RaisedButton = require('material-ui/RaisedButton').default
class ShowMore extends React.Component {
static get propTypes () {
return {
defaultExpanded: PropTypes.bool,
hideLabel: PropTypes.string,
showLabel: PropTypes.string
}
}
static get defaultProps () {
return {
hideLabel: 'Hide more...',
showLabel: 'Show more...'
}
}
constructor (props) {
super(props)
this.state = {
expanded: !!this.props.defaultExpanded
}
this.handleClick = this.handleClick.bind(this)
}
handleClick () {
this.setState({
expanded: !this.state.expanded
})
}
render () {
const label = this.state.expanded
? this.props.hideLabel
: this.props.showLabel
return (
<div className='show-more' style={this.props.style}>
{this.state.expanded ? this.props.children : null}
<RaisedButton
className='control'
onClick={this.handleClick}
label={label}
/>
</div>
)
}
}
module.exports = ShowMore

View File

@@ -0,0 +1,45 @@
const React = require('react')
const { shell } = require('electron')
const ModalOKCancel = require('./modal-ok-cancel')
const { dispatcher } = require('../lib/dispatcher')
module.exports = class UnsupportedMediaModal extends React.Component {
render () {
const state = this.props.state
const err = state.modal.error
const message = (err && err.getMessage)
? err.getMessage()
: err
const onAction = state.modal.externalPlayerInstalled
? dispatcher('openExternalPlayer')
: () => this.onInstall()
const actionText = state.modal.externalPlayerInstalled
? 'PLAY IN ' + state.getExternalPlayerName().toUpperCase()
: 'INSTALL VLC'
const errorMessage = state.modal.externalPlayerNotFound
? 'Couldn\'t run external player. Please make sure it\'s installed.'
: ''
return (
<div>
<p><strong>Sorry, we can't play that file.</strong></p>
<p>{message}</p>
<ModalOKCancel
cancelText='CANCEL'
onCancel={dispatcher('backToList')}
okText={actionText}
onOK={onAction}
/>
<p className='error-text'>{errorMessage}</p>
</div>
)
}
onInstall () {
shell.openExternal('https://www.videolan.org/vlc/')
// TODO: dcposch send a dispatch rather than modifying state directly
const state = this.props.state
state.modal.externalPlayerInstalled = true // Assume they'll install it successfully
}
}

View File

@@ -0,0 +1,37 @@
const React = require('react')
const { shell } = require('electron')
const ModalOKCancel = require('./modal-ok-cancel')
const { dispatch } = require('../lib/dispatcher')
module.exports = class UpdateAvailableModal extends React.Component {
render () {
const state = this.props.state
return (
<div className='update-available-modal'>
<p><strong>A new version of WebTorrent is available: v{state.modal.version}</strong></p>
<p>
We have an auto-updater for Windows and Mac.
We don't have one for Linux yet, so you'll have to download the new version manually.
</p>
<ModalOKCancel
cancelText='SKIP THIS RELEASE'
onCancel={handleSkip}
okText='SHOW DOWNLOAD PAGE'
onOK={handleShow}
/>
</div>
)
function handleShow () {
// TODO: use the GitHub urls from config.js
shell.openExternal('https://github.com/webtorrent/webtorrent-desktop/releases')
dispatch('exitModal')
}
function handleSkip () {
dispatch('skipVersion', state.modal.version)
dispatch('exitModal')
}
}
}

View File

@@ -0,0 +1,17 @@
const { dispatch } = require('../lib/dispatcher')
module.exports = class AudioTracksController {
constructor (state) {
this.state = state
}
selectAudioTrack (ix) {
this.state.playing.audioTracks.selectedIndex = ix
dispatch('skip', 0.2) // HACK: hardcoded seek value for smooth audio change
}
toggleAudioTracksMenu () {
const audioTracks = this.state.playing.audioTracks
audioTracks.showMenu = !audioTracks.showMenu
}
}

View File

@@ -0,0 +1,13 @@
const { ipcRenderer } = require('electron')
module.exports = class FolderWatcherController {
start () {
console.log('-- IPC: start folder watcher')
ipcRenderer.send('startFolderWatcher')
}
stop () {
console.log('-- IPC: stop folder watcher')
ipcRenderer.send('stopFolderWatcher')
}
}

View File

@@ -1,6 +1,6 @@
const electron = require('electron')
const ipcRenderer = electron.ipcRenderer
const { ipcRenderer } = require('electron')
const telemetry = require('../lib/telemetry')
const Playlist = require('../lib/playlist')
// Controls local play back: the <video>/<audio> tag and VLC
// Does not control remote casting (Chromecast etc)
@@ -10,7 +10,7 @@ module.exports = class MediaController {
}
mediaSuccess () {
this.state.playing.result = 'success'
telemetry.logPlayAttempt('success')
}
mediaStalled () {
@@ -18,16 +18,16 @@ module.exports = class MediaController {
}
mediaError (error) {
var state = this.state
const state = this.state
if (state.location.url() === 'player') {
state.playing.result = 'error'
telemetry.logPlayAttempt('error')
state.playing.location = 'error'
ipcRenderer.send('checkForVLC')
ipcRenderer.once('checkForVLC', function (e, isInstalled) {
ipcRenderer.send('checkForExternalPlayer', state.saved.prefs.externalPlayerPath)
ipcRenderer.once('checkForExternalPlayer', (e, isInstalled) => {
state.modal = {
id: 'unsupported-media-modal',
error: error,
vlcInstalled: isInstalled
error,
externalPlayerInstalled: isInstalled
}
})
}
@@ -42,15 +42,39 @@ module.exports = class MediaController {
this.state.playing.mouseStationarySince = new Date().getTime()
}
vlcPlay () {
ipcRenderer.send('vlcPlay', this.state.server.localURL, this.state.window.title)
this.state.playing.location = 'vlc'
controlsMouseEnter () {
this.state.playing.mouseInControls = true
this.state.playing.mouseStationarySince = new Date().getTime()
}
vlcNotFound () {
var modal = this.state.modal
controlsMouseLeave () {
this.state.playing.mouseInControls = false
this.state.playing.mouseStationarySince = new Date().getTime()
}
openExternalPlayer () {
const state = this.state
state.playing.location = 'external'
const onServerRunning = () => {
state.playing.isReady = true
telemetry.logPlayAttempt('external')
const mediaURL = Playlist.getCurrentLocalURL(state)
ipcRenderer.send('openExternalPlayer',
state.saved.prefs.externalPlayerPath,
mediaURL,
state.window.title)
}
if (state.server != null) onServerRunning()
else ipcRenderer.once('wt-server-running', onServerRunning)
}
externalPlayerNotFound () {
const modal = this.state.modal
if (modal && modal.id === 'unsupported-media-modal') {
modal.vlcNotFound = true
modal.externalPlayerNotFound = true
}
}
}

View File

@@ -1,19 +1,18 @@
const electron = require('electron')
const { ipcRenderer } = require('electron')
const path = require('path')
const Cast = require('../lib/cast')
const {dispatch} = require('../lib/dispatcher')
const { dispatch } = require('../lib/dispatcher')
const telemetry = require('../lib/telemetry')
const errors = require('../lib/errors')
const { UnplayableFileError, UnplayableTorrentError } = require('../lib/errors')
const sound = require('../lib/sound')
const TorrentPlayer = require('../lib/torrent-player')
const TorrentSummary = require('../lib/torrent-summary')
const Playlist = require('../lib/playlist')
const State = require('../lib/state')
const ipcRenderer = electron.ipcRenderer
// Controls playback of torrents and files within torrents
// both local (<video>,<audio>,VLC) and remote (cast)
// both local (<video>,<audio>,external player) and remote (cast)
module.exports = class PlaybackController {
constructor (state, config, update) {
this.state = state
@@ -24,37 +23,58 @@ module.exports = class PlaybackController {
// Play a file in a torrent.
// * Start torrenting, if necessary
// * Stream, if not already fully downloaded
// * If no file index is provided, pick the default file to play
// * If no file index is provided, restore the most recently viewed file or autoplay the first
playFile (infoHash, index /* optional */) {
this.state.location.go({
url: 'player',
setup: (cb) => {
this.play()
this.openPlayer(infoHash, index, cb)
},
destroy: () => this.closePlayer()
}, (err) => {
if (err) dispatch('error', err)
})
this.pauseActiveTorrents(infoHash)
const state = this.state
if (state.location.url() === 'player') {
this.updatePlayer(infoHash, index, false, (err) => {
if (err) dispatch('error', err)
else this.play()
})
} else {
let initialized = false
state.location.go({
url: 'player',
setup: (cb) => {
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
if (index === undefined || initialized) index = torrentSummary.mostRecentFileIndex
if (index === undefined) index = torrentSummary.files.findIndex(TorrentPlayer.isPlayable)
if (index === undefined) return cb(new UnplayableTorrentError())
initialized = true
this.openPlayer(infoHash, index, (err) => {
if (!err) this.play()
cb(err)
})
},
destroy: () => this.closePlayer()
}, (err) => {
if (err) dispatch('error', err)
})
}
}
// Show a file in the OS, eg in Finder on a Mac
openItem (infoHash, index) {
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
var filePath = path.join(
// Open a file in OS default app.
openPath (infoHash, index) {
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
const filePath = path.join(
torrentSummary.path,
torrentSummary.files[index].path)
ipcRenderer.send('openItem', filePath)
ipcRenderer.send('openPath', filePath)
}
// Toggle (play or pause) the currently playing media
playPause () {
var state = this.state
const state = this.state
if (state.location.url() !== 'player') return
// force rerendering if window is hidden,
// in order to bypass `raf` and play/pause media immediately
var mediaTag = document.querySelector('video,audio')
const mediaTag = document.querySelector('video,audio')
if (!state.window.isVisible && mediaTag) {
if (state.playing.isPaused) mediaTag.play()
else mediaTag.pause()
@@ -64,9 +84,44 @@ module.exports = class PlaybackController {
else this.pause()
}
pauseActiveTorrents (infoHash) {
// Playback Priority: pause all active torrents if needed.
if (!this.state.saved.prefs.highestPlaybackPriority) return
// Do not pause active torrents if playing a fully downloaded torrent.
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
if (torrentSummary.status === 'seeding') return
dispatch('prioritizeTorrent', infoHash)
}
// Play next file in list (if any)
nextTrack () {
const state = this.state
if (Playlist.hasNext(state) && state.playing.location !== 'external') {
this.updatePlayer(
state.playing.infoHash, Playlist.getNextIndex(state), false, (err) => {
if (err) dispatch('error', err)
else this.play()
})
}
}
// Play previous track in list (if any)
previousTrack () {
const state = this.state
if (Playlist.hasPrevious(state) && state.playing.location !== 'external') {
this.updatePlayer(
state.playing.infoHash, Playlist.getPreviousIndex(state), false, (err) => {
if (err) dispatch('error', err)
else this.play()
})
}
}
// Play (unpause) the current media
play () {
var state = this.state
const state = this.state
if (!state.playing.isPaused) return
state.playing.isPaused = false
if (isCasting(state)) {
@@ -77,7 +132,7 @@ module.exports = class PlaybackController {
// Pause the currently playing media
pause () {
var state = this.state
const state = this.state
if (state.playing.isPaused) return
state.playing.isPaused = true
if (isCasting(state)) {
@@ -93,34 +148,50 @@ module.exports = class PlaybackController {
// Skip (aka seek) to a specific point, in seconds
skipTo (time) {
if (!Number.isFinite(time)) {
console.error('Tried to skip to a non-finite time ' + time)
return console.trace()
}
if (isCasting(this.state)) Cast.seek(time)
else this.state.playing.jumpToTime = time
}
// Show video preview
preview (x) {
if (!Number.isFinite(x)) {
console.error('Tried to preview a non-finite position ' + x)
return console.trace()
}
this.state.playing.previewXCoord = x
}
// Hide video preview
clearPreview () {
this.state.playing.previewXCoord = null
}
// Change playback speed. 1 = faster, -1 = slower
// Playback speed ranges from 16 (fast forward) to 1 (normal playback)
// to 0.25 (quarter-speed playback), then goes to -0.25, -0.5, -1, -2, etc
// until -16 (fast rewind)
changePlaybackRate (direction) {
var state = this.state
var rate = state.playing.playbackRate
if (direction > 0 && rate >= 0.25 && rate < 2) {
const state = this.state
let rate = state.playing.playbackRate
if (direction > 0 && rate < 2) {
rate += 0.25
} else if (direction < 0 && rate > 0.25 && rate <= 2) {
rate -= 0.25
} else if (direction < 0 && rate === 0.25) { /* when we set playback rate at 0 in html 5, playback hangs ;( */
rate = -1
} else if (direction > 0 && rate === -1) {
rate = 0.25
} else if ((direction > 0 && rate >= 1 && rate < 16) || (direction < 0 && rate > -16 && rate <= -1)) {
} else if (direction > 0 && rate >= 1 && rate < 16) {
rate *= 2
} else if ((direction < 0 && rate > 1 && rate <= 16) || (direction > 0 && rate >= -16 && rate < -1)) {
} else if (direction < 0 && rate > 1 && rate <= 16) {
rate /= 2
}
state.playing.playbackRate = rate
if (isCasting(state) && !Cast.setRate(rate)) {
state.playing.playbackRate = 1
}
// Wait a bit before we hide the controls and header again
state.playing.mouseStationarySince = new Date().getTime()
}
// Change the volume, in range [0, 1], by some amount
@@ -135,7 +206,7 @@ module.exports = class PlaybackController {
// check if its in [0.0 - 1.0] range
volume = Math.max(0, Math.min(1, volume))
var state = this.state
const state = this.state
if (isCasting(state)) {
Cast.setVolume(volume)
} else {
@@ -149,12 +220,8 @@ module.exports = class PlaybackController {
// * The video is paused
// * The video is playing remotely on Chromecast or Airplay
showOrHidePlayerControls () {
var state = this.state
var hideControls = state.location.url() === 'player' &&
state.playing.mouseStationarySince !== 0 &&
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
!state.playing.isPaused &&
state.playing.location === 'local'
const state = this.state
const hideControls = state.shouldHidePlayerControls()
if (hideControls !== state.playing.hideControls) {
state.playing.hideControls = hideControls
@@ -165,60 +232,84 @@ module.exports = class PlaybackController {
// Opens the video player to a specific torrent
openPlayer (infoHash, index, cb) {
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
const state = this.state
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
// automatically choose which file in the torrent to play, if necessary
if (index === undefined) index = torrentSummary.defaultPlayFileIndex
if (index === undefined) index = TorrentPlayer.pickFileToPlay(torrentSummary.files)
if (index === undefined) return cb(new errors.UnplayableError())
state.playing.infoHash = torrentSummary.infoHash
state.playing.isReady = false
// update UI to show pending playback
if (torrentSummary.progress !== 1) sound.play('PLAY')
// TODO: remove torrentSummary.playStatus
torrentSummary.playStatus = 'requested'
this.update()
sound.play('PLAY')
var timeout = setTimeout(() => {
telemetry.logPlayAttempt('timeout')
// TODO: remove torrentSummary.playStatus
torrentSummary.playStatus = 'timeout' /* no seeders available? */
sound.play('ERROR')
cb(new Error('Playback timed out. Try again.'))
this.update()
}, 10000) /* give it a few seconds */
this.startServer(torrentSummary)
ipcRenderer.send('onPlayerOpen')
this.updatePlayer(infoHash, index, true, cb)
}
// Starts WebTorrent server for media streaming
startServer (torrentSummary) {
const state = this.state
if (torrentSummary.status === 'paused') {
dispatch('startTorrentingSummary', torrentSummary.torrentKey)
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash,
() => this.openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb))
() => onTorrentReady())
} else {
this.openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb)
onTorrentReady()
}
function onTorrentReady () {
ipcRenderer.send('wt-start-server', torrentSummary.infoHash)
ipcRenderer.once('wt-server-running', () => { state.playing.isReady = true })
}
}
openPlayerFromActiveTorrent (torrentSummary, index, timeout, cb) {
var fileSummary = torrentSummary.files[index]
// Called each time the current file changes
updatePlayer (infoHash, index, resume, cb) {
const state = this.state
// update state
var state = this.state
state.playing.infoHash = torrentSummary.infoHash
state.playing.fileIndex = index
state.playing.type = TorrentPlayer.isVideo(fileSummary) ? 'video'
: TorrentPlayer.isAudio(fileSummary) ? 'audio'
: 'other'
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
const fileSummary = torrentSummary.files[index]
// pick up where we left off
if (fileSummary.currentTime) {
var fraction = fileSummary.currentTime / fileSummary.duration
var secondsLeft = fileSummary.duration - fileSummary.currentTime
if (fraction < 0.9 && secondsLeft > 10) {
state.playing.jumpToTime = fileSummary.currentTime
}
if (!TorrentPlayer.isPlayable(fileSummary)) {
torrentSummary.mostRecentFileIndex = undefined
return cb(new UnplayableFileError())
}
torrentSummary.mostRecentFileIndex = index
// update state
state.playing.infoHash = infoHash
state.playing.fileIndex = index
state.playing.fileName = fileSummary.name
state.playing.type = TorrentPlayer.isVideo(fileSummary)
? 'video'
: TorrentPlayer.isAudio(fileSummary)
? 'audio'
: 'other'
// pick up where we left off
let jumpToTime = 0
if (resume && fileSummary.currentTime) {
const fraction = fileSummary.currentTime / fileSummary.duration
const secondsLeft = fileSummary.duration - fileSummary.currentTime
if (fraction < 0.9 && secondsLeft > 10) {
jumpToTime = fileSummary.currentTime
}
}
state.playing.jumpToTime = jumpToTime
// if it's audio, parse out the metadata (artist, title, etc)
if (state.playing.type === 'audio' && !fileSummary.audioInfo) {
ipcRenderer.send('wt-get-audio-metadata', torrentSummary.infoHash, index)
if (torrentSummary.status === 'paused') {
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash, getAudioMetadata)
} else {
getAudioMetadata()
}
function getAudioMetadata () {
if (state.playing.type === 'audio') {
ipcRenderer.send('wt-get-audio-metadata', torrentSummary.infoHash, index)
}
}
// if it's video, check for subtitles files that are done downloading
@@ -229,56 +320,39 @@ module.exports = class PlaybackController {
dispatch('addSubtitles', [fileSummary.selectedSubtitle], true)
}
ipcRenderer.send('wt-start-server', torrentSummary.infoHash, index)
ipcRenderer.once('wt-server-' + torrentSummary.infoHash, (e, info) => {
clearTimeout(timeout)
state.window.title = fileSummary.name
// if we timed out (user clicked play a long time ago), don't autoplay
var timedOut = torrentSummary.playStatus === 'timeout'
delete torrentSummary.playStatus
if (timedOut) {
ipcRenderer.send('wt-stop-server')
return this.update()
}
// play in VLC if set as default player (Preferences / Playback / Play in VLC)
if (this.state.saved.prefs.playInVlc) {
dispatch('vlcPlay')
this.update()
cb()
return
}
// otherwise, play the video
state.window.title = torrentSummary.files[state.playing.fileIndex].name
// play in VLC if set as default player (Preferences / Playback / Play in VLC)
if (this.state.saved.prefs.openExternalPlayer) {
dispatch('openExternalPlayer')
this.update()
ipcRenderer.send('onPlayerOpen')
cb()
})
return
}
// otherwise, play the video
this.update()
ipcRenderer.send('onPlayerUpdate', Playlist.hasNext(state), Playlist.hasPrevious(state))
cb()
}
closePlayer () {
console.log('closePlayer')
// Quit any external players, like Chromecast/Airplay/etc or VLC
var state = this.state
const state = this.state
if (isCasting(state)) {
Cast.stop()
}
if (state.playing.location === 'vlc') {
ipcRenderer.send('vlcQuit')
if (state.playing.location === 'external') {
ipcRenderer.send('quitExternalPlayer')
}
// Save volume (this session only, not in state.saved)
state.previousVolume = state.playing.volume
// Telemetry: track what happens after the user clicks play
var result = state.playing.result // 'success' or 'error'
if (result === 'success') telemetry.logPlayAttempt('success') // first frame displayed
else if (result === 'error') telemetry.logPlayAttempt('error') // codec missing, etc
else if (result === undefined) telemetry.logPlayAttempt('abandoned') // user exited before first frame
else console.error('Unknown state.playing.result', state.playing.result)
if (!state.playing.isReady) telemetry.logPlayAttempt('abandoned') // user gave up waiting
// Reset the window contents back to the home screen
state.playing = State.getDefaultPlayState()
@@ -295,6 +369,11 @@ module.exports = class PlaybackController {
ipcRenderer.send('onPlayerClose')
// Playback Priority: resume previously paused downloads.
if (this.state.saved.prefs.highestPlaybackPriority) {
dispatch('resumePausedTorrents')
}
this.update()
}
}

View File

@@ -1,6 +1,5 @@
const State = require('../lib/state')
const {dispatch} = require('../lib/dispatcher')
const ipcRenderer = require('electron').ipcRenderer
const { dispatch } = require('../lib/dispatcher')
const { ipcRenderer } = require('electron')
// Controls the Preferences screen
module.exports = class PrefsController {
@@ -11,46 +10,29 @@ module.exports = class PrefsController {
// Goes to the Preferences screen
show () {
var state = this.state
const state = this.state
state.location.go({
url: 'preferences',
setup: function (cb) {
setup (cb) {
// initialize preferences
state.window.title = 'Preferences'
state.unsaved = Object.assign(state.unsaved || {}, {prefs: state.saved.prefs || {}})
ipcRenderer.send('setAllowNav', false)
cb()
},
destroy: () => {
ipcRenderer.send('setAllowNav', true)
this.save()
}
})
}
// Updates a single property in the UNSAVED prefs
// For example: updatePreferences('foo.bar', 'baz')
// Call save() to save to config.json
// Updates a single property in the saved prefs
// For example: updatePreferences('isFileHandler', true)
update (property, value) {
var path = property.split('.')
var key = this.state.unsaved.prefs
for (var i = 0; i < path.length - 1; i++) {
if (typeof key[path[i]] === 'undefined') {
key[path[i]] = {}
}
key = key[path[i]]
}
key[path[i]] = value
}
if (property === 'isFileHandler') ipcRenderer.send('setDefaultFileHandler', value)
else if (property === 'startup') ipcRenderer.send('setStartup', value)
// All unsaved prefs take effect atomically, and are saved to config.json
save () {
var state = this.state
if (state.unsaved.prefs.isFileHandler !== state.saved.prefs.isFileHandler) {
ipcRenderer.send('setDefaultFileHandler', state.unsaved.prefs.isFileHandler)
}
state.saved.prefs = Object.assign(state.saved.prefs || {}, state.unsaved.prefs)
State.save(state)
this.state.saved.prefs[property] = value
dispatch('stateSaveImmediate')
dispatch('checkDownloadPath')
}
}

View File

@@ -1,9 +1,9 @@
const electron = require('electron')
const fs = require('fs-extra')
const remote = require('@electron/remote')
const fs = require('fs')
const path = require('path')
const parallel = require('run-parallel')
const {dispatch} = require('../lib/dispatcher')
const { dispatch } = require('../lib/dispatcher')
module.exports = class SubtitlesController {
constructor (state) {
@@ -11,14 +11,13 @@ module.exports = class SubtitlesController {
}
openSubtitles () {
electron.remote.dialog.showOpenDialog({
const filenames = remote.dialog.showOpenDialogSync({
title: 'Select a subtitles file.',
filters: [ { name: 'Subtitles', extensions: ['vtt', 'srt'] } ],
properties: [ 'openFile' ]
}, (filenames) => {
if (!Array.isArray(filenames)) return
this.addSubtitles(filenames, true)
filters: [{ name: 'Subtitles', extensions: ['vtt', 'srt'] }],
properties: ['openFile']
})
if (!Array.isArray(filenames)) return
this.addSubtitles(filenames, true)
}
selectSubtitle (ix) {
@@ -26,38 +25,36 @@ module.exports = class SubtitlesController {
}
toggleSubtitlesMenu () {
var subtitles = this.state.playing.subtitles
const subtitles = this.state.playing.subtitles
subtitles.showMenu = !subtitles.showMenu
}
addSubtitles (files, autoSelect) {
var state = this.state
// Subtitles are only supported when playing video files
if (state.playing.type !== 'video') return
if (this.state.playing.type !== 'video') return
if (files.length === 0) return
var subtitles = state.playing.subtitles
const subtitles = this.state.playing.subtitles
// Read the files concurrently, then add all resulting subtitle tracks
var tasks = files.map((file) => (cb) => loadSubtitle(file, cb))
parallel(tasks, function (err, tracks) {
const tasks = files.map((file) => (cb) => loadSubtitle(file, cb))
parallel(tasks, (err, tracks) => {
if (err) return dispatch('error', err)
for (var i = 0; i < tracks.length; i++) {
// No dupes allowed
var track = tracks[i]
var trackIndex = state.playing.subtitles.tracks
.findIndex((t) => track.filePath === t.filePath)
// No dupes allowed
tracks.forEach((track, i) => {
let trackIndex = subtitles.tracks.findIndex((t) =>
track.filePath === t.filePath)
// Add the track
if (trackIndex === -1) {
trackIndex = state.playing.subtitles.tracks.push(track) - 1
trackIndex = subtitles.tracks.push(track) - 1
}
// If we're auto-selecting a track, try to find one in the user's language
if (autoSelect && (i === 0 || isSystemLanguage(track.language))) {
state.playing.subtitles.selectedIndex = trackIndex
subtitles.selectedIndex = trackIndex
}
}
})
// Finally, make sure no two tracks have the same label
relabelSubtitles(subtitles)
@@ -66,50 +63,50 @@ module.exports = class SubtitlesController {
checkForSubtitles () {
if (this.state.playing.type !== 'video') return
var torrentSummary = this.state.getPlayingTorrentSummary()
const torrentSummary = this.state.getPlayingTorrentSummary()
if (!torrentSummary || !torrentSummary.progress) return
torrentSummary.progress.files.forEach((fp, ix) => {
if (fp.numPieces !== fp.numPiecesPresent) return // ignore incomplete files
var file = torrentSummary.files[ix]
const file = torrentSummary.files[ix]
if (!this.isSubtitle(file.name)) return
var filePath = path.join(torrentSummary.path, file.path)
const filePath = path.join(torrentSummary.path, file.path)
this.addSubtitles([filePath], false)
})
}
isSubtitle (file) {
var name = typeof file === 'string' ? file : file.name
var ext = path.extname(name).toLowerCase()
const name = typeof file === 'string' ? file : file.name
const ext = path.extname(name).toLowerCase()
return ext === '.srt' || ext === '.vtt'
}
}
function loadSubtitle (file, cb) {
// Lazy load to keep startup fast
var concat = require('simple-concat')
var LanguageDetect = require('languagedetect')
var srtToVtt = require('srt-to-vtt')
const concat = require('simple-concat')
const LanguageDetect = require('languagedetect')
const srtToVtt = require('srt-to-vtt')
// Read the .SRT or .VTT file, parse it, add subtitle track
var filePath = file.path || file
const filePath = file.path || file
var vttStream = fs.createReadStream(filePath).pipe(srtToVtt())
const vttStream = fs.createReadStream(filePath).pipe(srtToVtt())
concat(vttStream, function (err, buf) {
concat(vttStream, (err, buf) => {
if (err) return dispatch('error', 'Can\'t parse subtitles file.')
// Detect what language the subtitles are in
var vttContents = buf.toString().replace(/(.*-->.*)/g, '')
var langDetected = (new LanguageDetect()).detect(vttContents, 2)
const vttContents = buf.toString().replace(/(.*-->.*)/g, '')
let langDetected = (new LanguageDetect()).detect(vttContents, 2)
langDetected = langDetected.length ? langDetected[0][0] : 'subtitle'
langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1)
var track = {
const track = {
buffer: 'data:text/vtt;base64,' + buf.toString('base64'),
language: langDetected,
label: langDetected,
filePath: filePath
filePath
}
cb(null, track)
@@ -119,18 +116,18 @@ function loadSubtitle (file, cb) {
// Checks whether a language name like 'English' or 'German' matches the system
// language, aka the current locale
function isSystemLanguage (language) {
var iso639 = require('iso-639-1')
var osLangISO = window.navigator.language.split('-')[0] // eg 'en'
var langIso = iso639.getCode(language) // eg 'de' if language is 'German'
const iso639 = require('iso-639-1')
const osLangISO = window.navigator.language.split('-')[0] // eg 'en'
const langIso = iso639.getCode(language) // eg 'de' if language is 'German'
return langIso === osLangISO
}
// Make sure we don't have two subtitle tracks with the same label
// Labels each track by language, eg 'German', 'English', 'English 2', ...
function relabelSubtitles (subtitles) {
var counts = {}
subtitles.tracks.forEach(function (track) {
var lang = track.language
const counts = {}
subtitles.tracks.forEach(track => {
const lang = track.language
counts[lang] = (counts[lang] || 0) + 1
track.label = counts[lang] > 1 ? (lang + ' ' + counts[lang]) : lang
})

View File

@@ -1,23 +1,22 @@
const path = require('path')
const ipcRenderer = require('electron').ipcRenderer
const { ipcRenderer } = require('electron')
const TorrentSummary = require('../lib/torrent-summary')
const TorrentPlayer = require('../lib/torrent-player')
const sound = require('../lib/sound')
const {dispatch} = require('../lib/dispatcher')
const { dispatch } = require('../lib/dispatcher')
module.exports = class TorrentController {
constructor (state) {
this.state = state
}
torrentInfoHash (torrentKey, infoHash) {
var torrentSummary = this.getTorrentSummary(torrentKey)
torrentParsed (torrentKey, infoHash, magnetURI) {
let torrentSummary = this.getTorrentSummary(torrentKey)
console.log('got infohash for %s torrent %s',
torrentSummary ? 'existing' : 'new', torrentKey)
if (!torrentSummary) {
var torrents = this.state.saved.torrents
const torrents = this.state.saved.torrents
// Check if an existing (non-active) torrent has the same info hash
if (torrents.find((t) => t.infoHash === infoHash)) {
@@ -26,7 +25,7 @@ module.exports = class TorrentController {
}
torrentSummary = {
torrentKey: torrentKey,
torrentKey,
status: 'new'
}
torrents.unshift(torrentSummary)
@@ -34,6 +33,7 @@ module.exports = class TorrentController {
}
torrentSummary.infoHash = infoHash
torrentSummary.magnetURI = magnetURI
dispatch('update')
}
@@ -49,7 +49,7 @@ module.exports = class TorrentController {
}
dispatch('error', message)
var torrentSummary = this.getTorrentSummary(torrentKey)
const torrentSummary = this.getTorrentSummary(torrentKey)
if (torrentSummary) {
console.log('Pausing torrent %s due to error: %s', torrentSummary.infoHash, message)
torrentSummary.status = 'paused'
@@ -59,21 +59,19 @@ module.exports = class TorrentController {
torrentMetadata (torrentKey, torrentInfo) {
// Summarize torrent
var torrentSummary = this.getTorrentSummary(torrentKey)
const torrentSummary = this.getTorrentSummary(torrentKey)
torrentSummary.status = 'downloading'
torrentSummary.name = torrentSummary.displayName || torrentInfo.name
torrentSummary.path = torrentInfo.path
torrentSummary.magnetURI = torrentInfo.magnetURI
// TODO: make torrentInfo immutable, save separately as torrentSummary.info
// For now, check whether torrentSummary.files has already been set:
var hasDetailedFileInfo = torrentSummary.files && torrentSummary.files[0].path
const hasDetailedFileInfo = torrentSummary.files && torrentSummary.files[0].path
if (!hasDetailedFileInfo) {
torrentSummary.files = torrentInfo.files
}
if (!torrentSummary.selections) {
torrentSummary.selections = torrentSummary.files.map((x) => true)
}
torrentSummary.defaultPlayFileIndex = TorrentPlayer.pickFileToPlay(torrentInfo.files)
dispatch('update')
// Save the .torrent file, if it hasn't been saved already
@@ -85,7 +83,7 @@ module.exports = class TorrentController {
torrentDone (torrentKey, torrentInfo) {
// Update the torrent summary
var torrentSummary = this.getTorrentSummary(torrentKey)
const torrentSummary = this.getTorrentSummary(torrentKey)
torrentSummary.status = 'seeding'
// Notify the user that a torrent finished, but only if we actually DL'd at least part of it.
@@ -102,22 +100,18 @@ module.exports = class TorrentController {
}
torrentProgress (progressInfo) {
// Overall progress across all active torrents, 0 to 1
var progress = progressInfo.progress
var hasActiveTorrents = progressInfo.hasActiveTorrents
// Overall progress across all active torrents, 0 to 1, or -1 to hide the progress bar
// Hide progress bar when client has no torrents, or progress is 100%
// TODO: isn't this equivalent to: if (progress === 1) ?
if (!hasActiveTorrents || progress === 1) {
progress = -1
}
const progress = (!progressInfo.hasActiveTorrents || progressInfo.progress === 1)
? -1
: progressInfo.progress
// Show progress bar under the WebTorrent taskbar icon, on OSX
this.state.dock.progress = progress
// Update progress for each individual torrent
progressInfo.torrents.forEach((p) => {
var torrentSummary = this.getTorrentSummary(p.torrentKey)
const torrentSummary = this.getTorrentSummary(p.torrentKey)
if (!torrentSummary) {
console.log('warning: got progress for missing torrent %s', p.torrentKey)
return
@@ -132,27 +126,28 @@ module.exports = class TorrentController {
}
torrentFileModtimes (torrentKey, fileModtimes) {
var torrentSummary = this.getTorrentSummary(torrentKey)
const torrentSummary = this.getTorrentSummary(torrentKey)
if (!torrentSummary) throw new Error('Not saving modtimes for deleted torrent ' + torrentKey)
torrentSummary.fileModtimes = fileModtimes
dispatch('saveStateThrottled')
dispatch('stateSave')
}
torrentFileSaved (torrentKey, torrentFileName) {
console.log('torrent file saved %s: %s', torrentKey, torrentFileName)
var torrentSummary = this.getTorrentSummary(torrentKey)
const torrentSummary = this.getTorrentSummary(torrentKey)
torrentSummary.torrentFileName = torrentFileName
dispatch('saveStateThrottled')
dispatch('stateSave')
}
torrentPosterSaved (torrentKey, posterFileName) {
var torrentSummary = this.getTorrentSummary(torrentKey)
const torrentSummary = this.getTorrentSummary(torrentKey)
torrentSummary.posterFileName = posterFileName
dispatch('saveStateThrottled')
dispatch('stateSave')
}
torrentAudioMetadata (infoHash, index, info) {
var torrentSummary = this.getTorrentSummary(infoHash)
var fileSummary = torrentSummary.files[index]
const torrentSummary = this.getTorrentSummary(infoHash)
const fileSummary = torrentSummary.files[index]
fileSummary.audioInfo = info
dispatch('update')
}
@@ -169,7 +164,7 @@ module.exports = class TorrentController {
}
function getTorrentPath (torrentSummary) {
var itemPath = TorrentSummary.getFileOrFolder(torrentSummary)
let itemPath = TorrentSummary.getFileOrFolder(torrentSummary)
if (torrentSummary.files.length > 1) {
itemPath = path.dirname(itemPath)
}
@@ -177,14 +172,15 @@ function getTorrentPath (torrentSummary) {
}
function showDoneNotification (torrent) {
var notif = new window.Notification('Download Complete', {
const notif = new window.Notification('Download Complete', {
body: torrent.name,
silent: true
})
notif.onClick = function () {
notif.onclick = () => {
ipcRenderer.send('show')
}
sound.play('DONE')
// Only play notification sound if player is inactive
if (this.state.playing.isPaused) sound.play('DONE')
}

View File

@@ -1,14 +1,13 @@
const fs = require('fs')
const path = require('path')
const electron = require('electron')
const { ipcRenderer, clipboard } = require('electron')
const remote = require('@electron/remote')
const {dispatch} = require('../lib/dispatcher')
const State = require('../lib/state')
const { dispatch } = require('../lib/dispatcher')
const { TorrentKeyNotFoundError } = require('../lib/errors')
const sound = require('../lib/sound')
const TorrentSummary = require('../lib/torrent-summary')
const ipcRenderer = electron.ipcRenderer
const instantIoRegex = /^(https:\/\/)?instant\.io\/#/
// Controls the torrent list: creating, adding, deleting, & manipulating torrents
@@ -17,21 +16,26 @@ module.exports = class TorrentListController {
this.state = state
}
// Adds a torrent to the list, starts downloading/seeding. TorrentID can be a
// magnet URI, infohash, or torrent file: https://github.com/feross/webtorrent#clientaddtorrentid-opts-function-ontorrent-torrent-
// Adds a torrent to the list, starts downloading/seeding.
// TorrentID can be a magnet URI, infohash, or torrent file: https://git.io/vik9M
addTorrent (torrentId) {
if (torrentId.path) {
// Use path string instead of W3C File object
torrentId = torrentId.path
}
// Trim extra spaces off pasted magnet links
if (typeof torrentId === 'string') {
torrentId = torrentId.trim()
}
// Allow a instant.io link to be pasted
if (typeof torrentId === 'string' && instantIoRegex.test(torrentId)) {
torrentId = torrentId.slice(torrentId.indexOf('#') + 1)
}
var torrentKey = this.state.nextTorrentKey++
var path = this.state.saved.prefs.downloadPath
const torrentKey = this.state.nextTorrentKey++
const path = this.state.saved.prefs.downloadPath
ipcRenderer.send('wt-start-torrenting', torrentKey, torrentId, path)
@@ -50,7 +54,7 @@ module.exports = class TorrentListController {
if (files.length === 0 || typeof files[0] !== 'string') {
this.state.location.go({
url: 'create-torrent',
files: files,
files,
setup: (cb) => {
this.state.window.title = 'Create New Torrent'
cb(null)
@@ -64,63 +68,130 @@ module.exports = class TorrentListController {
findFilesRecursive(files, (allFiles) => this.showCreateTorrent(allFiles))
}
// Switches between the advanced and simple Create Torrent UI
toggleCreateTorrentAdvanced () {
var info = this.state.location.current()
if (info.url !== 'create-torrent') return
info.showAdvanced = !info.showAdvanced
}
// Creates a new torrent and start seeeding
createTorrent (options) {
var state = this.state
var torrentKey = state.nextTorrentKey++
const state = this.state
const torrentKey = state.nextTorrentKey++
ipcRenderer.send('wt-create-torrent', torrentKey, options)
state.location.cancel()
}
// Starts downloading and/or seeding a given torrentSummary.
startTorrentingSummary (torrentKey) {
var s = TorrentSummary.getByKey(this.state, torrentKey)
if (!s) throw new Error('Missing key: ' + torrentKey)
const s = TorrentSummary.getByKey(this.state, torrentKey)
if (!s) throw new TorrentKeyNotFoundError(torrentKey)
// Use Downloads folder by default
if (!s.path) s.path = this.state.saved.prefs.downloadPath
// New torrent: give it a path
if (!s.path) {
// Use Downloads folder by default
s.path = this.state.saved.prefs.downloadPath
return start()
}
fs.stat(TorrentSummary.getFileOrFolder(s), function (err) {
const fileOrFolder = TorrentSummary.getFileOrFolder(s)
// New torrent: metadata not yet received
if (!fileOrFolder) return start()
// Existing torrent: check that the path is still there
fs.stat(fileOrFolder, err => {
if (err) {
s.error = 'path-missing'
dispatch('backToList')
return
}
start()
})
function start () {
ipcRenderer.send('wt-start-torrenting',
s.torrentKey,
TorrentSummary.getTorrentID(s),
TorrentSummary.getTorrentId(s),
s.path,
s.fileModtimes,
s.selections)
})
}
}
setGlobalTrackers (globalTrackers) {
ipcRenderer.send('wt-set-global-trackers', globalTrackers)
}
// TODO: use torrentKey, not infoHash
toggleTorrent (infoHash) {
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
if (torrentSummary.status === 'paused') {
torrentSummary.status = 'new'
this.startTorrentingSummary(torrentSummary.torrentKey)
sound.play('ENABLE')
} else {
torrentSummary.status = 'paused'
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
sound.play('DISABLE')
return
}
this.pauseTorrent(torrentSummary, true)
}
pauseAllTorrents () {
this.state.saved.torrents.forEach((torrentSummary) => {
if (torrentSummary.status === 'downloading' ||
torrentSummary.status === 'seeding') {
torrentSummary.status = 'paused'
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
}
})
sound.play('DISABLE')
}
resumeAllTorrents () {
this.state.saved.torrents.forEach((torrentSummary) => {
if (torrentSummary.status === 'paused') {
torrentSummary.status = 'downloading'
this.startTorrentingSummary(torrentSummary.torrentKey)
}
})
sound.play('ENABLE')
}
pauseTorrent (torrentSummary, playSound) {
torrentSummary.status = 'paused'
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
if (playSound) sound.play('DISABLE')
}
prioritizeTorrent (infoHash) {
this.state.saved.torrents
.filter(torrent => ['downloading', 'seeding'].includes(torrent.status)) // Active torrents only.
.forEach((torrent) => { // Pause all active torrents except the one that started playing.
if (infoHash === torrent.infoHash) return
// Pause torrent without playing sounds.
this.pauseTorrent(torrent, false)
this.state.saved.torrentsToResume.push(torrent.infoHash)
})
console.log('Playback Priority: paused torrents: ', this.state.saved.torrentsToResume)
}
resumePausedTorrents () {
console.log('Playback Priority: resuming paused torrents')
if (!this.state.saved.torrentsToResume || !this.state.saved.torrentsToResume.length) return
this.state.saved.torrentsToResume.forEach((infoHash) => {
this.toggleTorrent(infoHash)
})
// reset paused torrents
this.state.saved.torrentsToResume = []
}
toggleTorrentFile (infoHash, index) {
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
torrentSummary.selections[index] = !torrentSummary.selections[index]
// Let the WebTorrent process know to start or stop fetching that file
ipcRenderer.send('wt-select-files', infoHash, torrentSummary.selections)
if (torrentSummary.status !== 'paused') {
ipcRenderer.send('wt-select-files', infoHash, torrentSummary.selections)
}
}
confirmDeleteTorrent (infoHash, deleteData) {
@@ -131,28 +202,46 @@ module.exports = class TorrentListController {
}
}
confirmDeleteAllTorrents (deleteData) {
this.state.modal = {
id: 'delete-all-torrents-modal',
deleteData
}
}
// TODO: use torrentKey, not infoHash
deleteTorrent (infoHash, deleteData) {
ipcRenderer.send('wt-stop-torrenting', infoHash)
var index = this.state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
const index = this.state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
if (index > -1) {
var summary = this.state.saved.torrents[index]
// remove torrent and poster file
deleteFile(TorrentSummary.getTorrentPath(summary))
deleteFile(TorrentSummary.getPosterPath(summary)) // TODO: will the css path hack affect windows?
// optionally delete the torrent data
if (deleteData) moveItemToTrash(summary)
const summary = this.state.saved.torrents[index]
deleteTorrentFile(summary, deleteData)
// remove torrent from saved list
this.state.saved.torrents.splice(index, 1)
State.saveThrottled(this.state)
dispatch('stateSave')
// prevent user from going forward to a deleted torrent
this.state.location.clearForward('player')
sound.play('DELETE')
} else {
throw new TorrentKeyNotFoundError(infoHash)
}
}
deleteAllTorrents (deleteData) {
// Go back to list before the current playing torrent is deleted
if (this.state.location.url() === 'player') {
dispatch('backToList')
}
this.state.location.clearForward('player') // prevent user from going forward to a deleted torrent
this.state.saved.torrents.forEach((summary) => deleteTorrentFile(summary, deleteData))
this.state.saved.torrents = []
dispatch('stateSave')
// prevent user from going forward to a deleted torrent
this.state.location.clearForward('player')
sound.play('DELETE')
}
@@ -165,78 +254,120 @@ module.exports = class TorrentListController {
}
openTorrentContextMenu (infoHash) {
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
var menu = new electron.remote.Menu()
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
const menu = new remote.Menu()
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
label: 'Remove From List',
click: () => dispatch('confirmDeleteTorrent', torrentSummary.infoHash, false)
}))
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
label: 'Remove Data File',
click: () => dispatch('confirmDeleteTorrent', torrentSummary.infoHash, true)
}))
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
type: 'separator'
}))
if (torrentSummary.files) {
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
label: process.platform === 'darwin' ? 'Show in Finder' : 'Show in Folder',
click: () => showItemInFolder(torrentSummary)
}))
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
type: 'separator'
}))
}
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
label: 'Copy Magnet Link to Clipboard',
click: () => electron.clipboard.writeText(torrentSummary.magnetURI)
click: () => clipboard.writeText(torrentSummary.magnetURI)
}))
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
label: 'Copy Instant.io Link to Clipboard',
click: () => electron.clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
click: () => clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
}))
menu.append(new electron.remote.MenuItem({
menu.append(new remote.MenuItem({
label: 'Save Torrent File As...',
click: () => saveTorrentFileAs(torrentSummary)
click: () => dispatch('saveTorrentFileAs', torrentSummary.torrentKey),
enabled: torrentSummary.torrentFileName != null
}))
menu.popup(electron.remote.getCurrentWindow())
menu.append(new remote.MenuItem({
type: 'separator'
}))
const sortedByName = this.state.saved.prefs.sortByName
menu.append(new remote.MenuItem({
label: `${sortedByName ? '✓ ' : ''}Sort by Name`,
click: () => dispatch('updatePreferences', 'sortByName', !sortedByName)
}))
menu.popup({ window: remote.getCurrentWindow() })
}
// Takes a torrentSummary or torrentKey
// Shows a Save File dialog, then saves the .torrent file wherever the user requests
saveTorrentFileAs (torrentKey) {
const torrentSummary = TorrentSummary.getByKey(this.state, torrentKey)
if (!torrentSummary) throw new TorrentKeyNotFoundError(torrentKey)
const downloadPath = this.state.saved.prefs.downloadPath
const newFileName = path.parse(torrentSummary.name).name + '.torrent'
const win = remote.getCurrentWindow()
const opts = {
title: 'Save Torrent File',
defaultPath: path.join(downloadPath, newFileName),
filters: [
{ name: 'Torrent Files', extensions: ['torrent'] },
{ name: 'All Files', extensions: ['*'] }
],
buttonLabel: 'Save'
}
const savePath = remote.dialog.showSaveDialogSync(win, opts)
if (!savePath) return // They clicked Cancel
console.log('Saving torrent ' + torrentKey + ' to ' + savePath)
const torrentPath = TorrentSummary.getTorrentPath(torrentSummary)
fs.readFile(torrentPath, (err, torrentFile) => {
if (err) return dispatch('error', err)
fs.writeFile(savePath, torrentFile, err => {
if (err) return dispatch('error', err)
})
})
}
}
// Recursively finds {name, path, size} for all files in a folder
// Calls `cb` on success, calls `onError` on failure
function findFilesRecursive (paths, cb) {
function findFilesRecursive (paths, cb_) {
if (paths.length > 1) {
var numComplete = 0
var ret = []
paths.forEach(function (path) {
findFilesRecursive([path], function (fileObjs) {
ret = ret.concat(fileObjs)
let numComplete = 0
const ret = []
paths.forEach(path => {
findFilesRecursive([path], fileObjs => {
ret.push(...fileObjs)
if (++numComplete === paths.length) {
ret.sort((a, b) => a.path < b.path ? -1 : a.path > b.path)
cb(ret)
ret.sort((a, b) => a.path < b.path ? -1 : Number(a.path > b.path))
cb_(ret)
}
})
})
return
}
var fileOrFolder = paths[0]
fs.stat(fileOrFolder, function (err, stat) {
const fileOrFolder = paths[0]
fs.stat(fileOrFolder, (err, stat) => {
if (err) return dispatch('error', err)
// Files: return name, path, and size
if (!stat.isDirectory()) {
var filePath = fileOrFolder
return cb([{
const filePath = fileOrFolder
return cb_([{
name: path.basename(filePath),
path: filePath,
size: stat.size
@@ -244,51 +375,39 @@ function findFilesRecursive (paths, cb) {
}
// Folders: recurse, make a list of all the files
var folderPath = fileOrFolder
fs.readdir(folderPath, function (err, fileNames) {
const folderPath = fileOrFolder
fs.readdir(folderPath, (err, fileNames) => {
if (err) return dispatch('error', err)
var paths = fileNames.map((fileName) => path.join(folderPath, fileName))
findFilesRecursive(paths, cb)
const paths = fileNames.map((fileName) => path.join(folderPath, fileName))
findFilesRecursive(paths, cb_)
})
})
}
function deleteFile (path) {
if (!path) return
fs.unlink(path, function (err) {
fs.unlink(path, err => {
if (err) dispatch('error', err)
})
}
// Delete all files in a torrent
function moveItemToTrash (torrentSummary) {
var filePath = TorrentSummary.getFileOrFolder(torrentSummary)
ipcRenderer.send('moveItemToTrash', filePath)
const filePath = TorrentSummary.getFileOrFolder(torrentSummary)
if (filePath) ipcRenderer.send('moveItemToTrash', filePath)
}
function showItemInFolder (torrentSummary) {
ipcRenderer.send('showItemInFolder', TorrentSummary.getFileOrFolder(torrentSummary))
}
function saveTorrentFileAs (torrentSummary) {
var downloadPath = this.state.saved.prefs.downloadPath
var newFileName = path.parse(torrentSummary.name).name + '.torrent'
var opts = {
title: 'Save Torrent File',
defaultPath: path.join(downloadPath, newFileName),
filters: [
{ name: 'Torrent Files', extensions: ['torrent'] },
{ name: 'All Files', extensions: ['*'] }
]
}
electron.remote.dialog.showSaveDialog(electron.remote.getCurrentWindow(), opts, function (savePath) {
if (!savePath) return // They clicked Cancel
var torrentPath = TorrentSummary.getTorrentPath(torrentSummary)
fs.readFile(torrentPath, function (err, torrentFile) {
if (err) return dispatch('error', err)
fs.writeFile(savePath, torrentFile, function (err) {
if (err) return dispatch('error', err)
})
})
})
function deleteTorrentFile (torrentSummary, deleteData) {
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
// remove torrent and poster file
deleteFile(TorrentSummary.getTorrentPath(torrentSummary))
deleteFile(TorrentSummary.getPosterPath(torrentSummary))
// optionally delete the torrent data
if (deleteData) moveItemToTrash(torrentSummary)
}

View File

@@ -1,4 +1,4 @@
const State = require('../lib/state')
const { dispatch } = require('../lib/dispatcher')
// Controls the UI checking for new versions of the app, prompting install
module.exports = class UpdateController {
@@ -8,19 +8,19 @@ module.exports = class UpdateController {
// Shows a modal saying that we have an update
updateAvailable (version) {
var skipped = this.state.saved.skippedVersions
const skipped = this.state.saved.skippedVersions
if (skipped && skipped.includes(version)) {
console.log('new version skipped by user: v' + version)
return
}
this.state.modal = { id: 'update-available-modal', version: version }
this.state.modal = { id: 'update-available-modal', version }
}
// Don't show the modal again until the next version
skipVersion (version) {
var skipped = this.state.saved.skippedVersions
let skipped = this.state.saved.skippedVersions
if (!skipped) skipped = this.state.saved.skippedVersions = []
skipped.push(version)
State.saveThrottled(this.state)
dispatch('stateSave')
}
}

View File

@@ -1,30 +0,0 @@
module.exports = captureVideoFrame
function captureVideoFrame (video, format) {
if (typeof video === 'string') {
video = document.querySelector(video)
}
if (video == null || video.nodeName !== 'VIDEO') {
throw new Error('First argument must be a <video> element or selector')
}
if (format == null) {
format = 'png'
}
if (format !== 'png' && format !== 'jpg' && format !== 'webp') {
throw new Error('Second argument must be one of "png", "jpg", or "webp"')
}
var canvas = document.createElement('canvas')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
canvas.getContext('2d').drawImage(video, 0, 0)
var dataUri = canvas.toDataURL('image/' + format)
var data = dataUri.split(',')[1]
return new Buffer(data, 'base64')
}

View File

@@ -13,25 +13,37 @@ module.exports = {
setRate
}
// Lazy load these for a ~300ms improvement in startup time
var airplayer, chromecasts, dlnacasts
const http = require('http')
var config = require('../../config')
const config = require('../../config')
const { CastingError } = require('./errors')
// Lazy load these for a ~300ms improvement in startup time
let airplayer, chromecasts, dlnacasts
// App state. Cast modifies state.playing and state.errors in response to events
var state
let state
// Callback to notify module users when state has changed
var update
let update
// setInterval() for updating cast status
var statusInterval = null
let statusInterval = null
// Start looking for cast devices on the local network
function init (appState, callback) {
state = appState
update = callback
// Don't actually cast during integration tests
// (Otherwise you'd need a physical Chromecast + AppleTV + DLNA TV to run them.)
if (config.IS_TEST) {
state.devices.chromecast = testPlayer('chromecast')
state.devices.airplay = testPlayer('airplay')
state.devices.dlna = testPlayer('dlna')
return
}
// Load modules, scan the network for devices
airplayer = require('airplayer')()
chromecasts = require('chromecasts')()
@@ -42,24 +54,50 @@ function init (appState, callback) {
state.devices.airplay = airplayPlayer()
// Listen for devices: Chromecast, DLNA and Airplay
chromecasts.on('update', function (device) {
chromecasts.on('update', device => {
// TODO: how do we tell if there are *no longer* any Chromecasts available?
// From looking at the code, chromecasts.players only grows, never shrinks
state.devices.chromecast.addDevice(device)
})
dlnacasts.on('update', function (device) {
dlnacasts.on('update', device => {
state.devices.dlna.addDevice(device)
})
airplayer.on('update', function (device) {
airplayer.on('update', device => {
state.devices.airplay.addDevice(device)
})
}
// integration test player implementation
function testPlayer (type) {
return {
getDevices,
open,
play,
pause,
stop,
status,
seek,
volume
}
function getDevices () {
return [{ name: type + '-1' }, { name: type + '-2' }]
}
function open () {}
function play () {}
function pause () {}
function stop () {}
function status () {}
function seek () {}
function volume () {}
}
// chromecast player implementation
function chromecastPlayer () {
var ret = {
const ret = {
device: null,
addDevice,
getDevices,
@@ -78,7 +116,7 @@ function chromecastPlayer () {
}
function addDevice (device) {
device.on('error', function (err) {
device.on('error', err => {
if (device !== ret.device) return
state.playing.location = 'local'
state.errors.push({
@@ -87,29 +125,54 @@ function chromecastPlayer () {
})
update()
})
device.on('disconnect', function () {
device.on('disconnect', () => {
if (device !== ret.device) return
state.playing.location = 'local'
update()
})
}
function open () {
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
ret.device.play(state.server.networkURL, {
type: 'video/mp4',
title: config.APP_NAME + ' - ' + torrentSummary.name
}, function (err) {
if (err) {
state.playing.location = 'local'
state.errors.push({
time: new Date().getTime(),
message: 'Could not connect to Chromecast. ' + err.message
function serveSubtitles (callback) {
const subtitles = state.playing.subtitles
const selectedSubtitle = subtitles.tracks[subtitles.selectedIndex]
if (!selectedSubtitle) {
callback()
} else {
ret.subServer = http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/vtt;charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Transfer-Encoding': 'chunked'
})
} else {
state.playing.location = 'chromecast'
}
update()
res.end(Buffer.from(selectedSubtitle.buffer.slice(21), 'base64'))
}).listen(0, () => {
const port = ret.subServer.address().port
const subtitlesUrl = 'http://' + state.server.networkAddress + ':' + port + '/'
callback(subtitlesUrl)
})
}
}
function open () {
const torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
serveSubtitles(subtitlesUrl => {
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, {
type: 'video/mp4',
title: config.APP_NAME + ' - ' + torrentSummary.name,
subtitles: subtitlesUrl ? [subtitlesUrl] : [],
autoSubtitles: !!subtitlesUrl
}, err => {
if (err) {
state.playing.location = 'local'
state.errors.push({
time: new Date().getTime(),
message: 'Could not connect to Chromecast. ' + err.message
})
} else {
state.playing.location = 'chromecast'
}
update()
})
})
}
@@ -123,16 +186,13 @@ function chromecastPlayer () {
function stop (callback) {
ret.device.stop(callback)
if (ret.subServer) {
ret.subServer.close()
}
}
function status () {
ret.device.status(function (err, status) {
if (err) return console.log('error getting %s status: %o', state.playing.location, err)
state.playing.isPaused = status.playerState === 'PAUSED'
state.playing.currentTime = status.currentTime
state.playing.volume = status.volume.muted ? 0 : status.volume.level
update()
})
ret.device.status(handleStatus)
}
function seek (time, callback) {
@@ -146,7 +206,7 @@ function chromecastPlayer () {
// airplay player implementation
function airplayPlayer () {
var ret = {
const ret = {
device: null,
addDevice,
getDevices,
@@ -161,7 +221,7 @@ function airplayPlayer () {
return ret
function addDevice (player) {
player.on('event', function (event) {
player.on('event', event => {
switch (event.state) {
case 'loading':
break
@@ -183,7 +243,7 @@ function airplayPlayer () {
}
function open () {
ret.device.play(state.server.networkURL, function (err, res) {
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, (err, res) => {
if (err) {
state.playing.location = 'local'
state.errors.push({
@@ -210,7 +270,7 @@ function airplayPlayer () {
}
function status () {
ret.device.playbackInfo(function (err, res, status) {
ret.device.playbackInfo((err, res, status) => {
if (err) {
state.playing.location = 'local'
state.errors.push({
@@ -238,7 +298,7 @@ function airplayPlayer () {
// DLNA player implementation
function dlnaPlayer (player) {
var ret = {
const ret = {
device: null,
addDevice,
getDevices,
@@ -257,7 +317,7 @@ function dlnaPlayer (player) {
}
function addDevice (device) {
device.on('error', function (err) {
device.on('error', err => {
if (device !== ret.device) return
state.playing.location = 'local'
state.errors.push({
@@ -266,7 +326,7 @@ function dlnaPlayer (player) {
})
update()
})
device.on('disconnect', function () {
device.on('disconnect', () => {
if (device !== ret.device) return
state.playing.location = 'local'
update()
@@ -274,12 +334,12 @@ function dlnaPlayer (player) {
}
function open () {
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
ret.device.play(state.server.networkURL, {
const torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, {
type: 'video/mp4',
title: config.APP_NAME + ' - ' + torrentSummary.name,
seek: state.playing.currentTime > 10 ? state.playing.currentTime : 0
}, function (err) {
}, err => {
if (err) {
state.playing.location = 'local'
state.errors.push({
@@ -306,13 +366,7 @@ function dlnaPlayer (player) {
}
function status () {
ret.device.status(function (err, status) {
if (err) return console.log('error getting %s status: %o', state.playing.location, err)
state.playing.isPaused = status.playerState === 'PAUSED'
state.playing.currentTime = status.currentTime
state.playing.volume = status.volume.level
update()
})
ret.device.status(handleStatus)
}
function seek (time, callback) {
@@ -320,7 +374,7 @@ function dlnaPlayer (player) {
}
function volume (volume, callback) {
ret.device.volume(volume, function (err) {
ret.device.volume(volume, err => {
// quick volume update
state.playing.volume = volume
callback(err)
@@ -328,10 +382,22 @@ function dlnaPlayer (player) {
}
}
function handleStatus (err, status) {
if (err || !status) {
return console.log('error getting %s status: %o',
state.playing.location,
err || 'missing response')
}
state.playing.isPaused = status.playerState === 'PAUSED'
state.playing.currentTime = status.currentTime
state.playing.volume = status.volume.muted ? 0 : status.volume.level
update()
}
// Start polling cast device state, whenever we're connected
function startStatusInterval () {
statusInterval = setInterval(function () {
var player = getPlayer()
statusInterval = setInterval(() => {
const player = getPlayer()
if (player) player.status()
}, 1000)
}
@@ -350,23 +416,27 @@ function toggleMenu (location) {
// Never cast to two devices at the same time
if (state.playing.location !== 'local') {
throw new Error('You can\'t connect to ' + location + ' when already connected to another device')
throw new CastingError(
`You can't connect to ${location} when already connected to another device`
)
}
// Find all cast devices of the given type
var player = getPlayer(location)
var devices = player ? player.getDevices() : []
if (devices.length === 0) throw new Error('No ' + location + ' devices available')
const player = getPlayer(location)
const devices = player ? player.getDevices() : []
if (devices.length === 0) {
throw new CastingError(`No ${location} devices available`)
}
// Show a menu
state.devices.castMenu = {location, devices}
state.devices.castMenu = { location, devices }
}
function selectDevice (index) {
var {location, devices} = state.devices.castMenu
const { location, devices } = state.devices.castMenu
// Start casting
var player = getPlayer(location)
const player = getPlayer(location)
player.device = devices[index]
player.open()
@@ -382,9 +452,9 @@ function selectDevice (index) {
// Stops casting, move video back to local screen
function stop () {
var player = getPlayer()
const player = getPlayer()
if (player) {
player.stop(function () {
player.stop(() => {
player.device = null
stoppedCasting()
})
@@ -396,7 +466,9 @@ function stop () {
function stoppedCasting () {
state.playing.location = 'local'
state.playing.jumpToTime = state.playing.currentTime
state.playing.jumpToTime = Number.isFinite(state.playing.currentTime)
? state.playing.currentTime
: 0
update()
}
@@ -415,18 +487,18 @@ function getPlayer (location) {
}
function play () {
var player = getPlayer()
const player = getPlayer()
if (player) player.play(castCallback)
}
function pause () {
var player = getPlayer()
const player = getPlayer()
if (player) player.pause(castCallback)
}
function setRate (rate) {
var player
var result = true
let player
let result = true
if (state.playing.location === 'chromecast') {
// TODO find how to control playback rate on chromecast
castCallback()
@@ -441,15 +513,15 @@ function setRate (rate) {
}
function seek (time) {
var player = getPlayer()
const player = getPlayer()
if (player) player.seek(time, castCallback)
}
function setVolume (volume) {
var player = getPlayer()
const player = getPlayer()
if (player) player.volume(volume, castCallback)
}
function castCallback () {
console.log('%s callback: %o', state.playing.location, arguments)
function castCallback (...args) {
console.log('%s callback: %o', state.playing.location, args)
}

View File

@@ -4,8 +4,8 @@ module.exports = {
setDispatch
}
var dispatchers = {}
var _dispatch = function () {}
const dispatchers = {}
let _dispatch = () => {}
function setDispatch (dispatch) {
_dispatch = dispatch
@@ -20,10 +20,10 @@ function dispatch (...args) {
// function. This prevents React from updating the listener functions on
// each update().
function dispatcher (...args) {
var str = JSON.stringify(args)
var handler = dispatchers[str]
const str = JSON.stringify(args)
let handler = dispatchers[str]
if (!handler) {
handler = dispatchers[str] = function (e) {
handler = dispatchers[str] = e => {
// Do not propagate click to elements below the button
e.stopPropagation()

View File

@@ -1,8 +1,41 @@
module.exports = {
UnplayableError
const ExtendableError = require('es6-error')
/* Generic errors */
class CastingError extends ExtendableError {}
class PlaybackError extends ExtendableError {}
class SoundError extends ExtendableError {}
class TorrentError extends ExtendableError {}
/* Playback */
class UnplayableTorrentError extends PlaybackError {
constructor () { super('Can\'t play any files in torrent') }
}
function UnplayableError () {
this.message = 'Can\'t play any files in torrent'
class UnplayableFileError extends PlaybackError {
constructor () { super('Can\'t play that file') }
}
/* Sound */
class InvalidSoundNameError extends SoundError {
constructor (name) { super(`Invalid sound name: ${name}`) }
}
/* Torrent */
class TorrentKeyNotFoundError extends TorrentError {
constructor (torrentKey) { super(`Can't resolve torrent key ${torrentKey}`) }
}
module.exports = {
CastingError,
PlaybackError,
SoundError,
TorrentError,
UnplayableTorrentError,
UnplayableFileError,
InvalidSoundNameError,
TorrentKeyNotFoundError
}
UnplayableError.prototype = Error

View File

@@ -0,0 +1,12 @@
const mediaExtensions = {
audio: [
'.aac', '.aif', '.aiff', '.asf', '.dff', '.dsf', '.flac', '.m2a',
'.m2a', '.m4a', '.mpc', '.m4b', '.mka', '.mp2', '.mp3', '.mpc', '.oga',
'.ogg', '.opus', '.spx', '.wma', '.wav', '.wv', '.wvp'],
video: [
'.avi', '.mp4', '.m4v', '.webm', '.mov', '.mkv', '.mpg', '.mpeg',
'.ogv', '.webm', '.wmv', '.m2ts'],
image: ['.gif', '.jpg', '.jpeg', '.png']
}
module.exports = mediaExtensions

View File

@@ -4,8 +4,11 @@ module.exports = {
run
}
var semver = require('semver')
var config = require('../../config')
const fs = require('fs')
const path = require('path')
const semver = require('semver')
const config = require('../../config')
// Change `state.saved` (which will be saved back to config.json on exit) as
// needed, for example to deal with config.json format changes across versions
@@ -15,32 +18,42 @@ function run (state) {
state.saved.version = '0.0.0' // Pre-0.7.0 version, so run all migrations
}
var version = state.saved.version
const version = state.saved.version
const saved = state.saved
if (semver.lt(version, '0.7.0')) {
migrate_0_7_0(state.saved)
}
if (semver.lt(version, '0.7.0')) migrate_0_7_0(saved)
if (semver.lt(version, '0.7.2')) migrate_0_7_2(saved)
if (semver.lt(version, '0.11.0')) migrate_0_11_0(saved)
if (semver.lt(version, '0.12.0')) migrate_0_12_0(saved)
if (semver.lt(version, '0.14.0')) migrate_0_14_0(saved)
if (semver.lt(version, '0.17.0')) migrate_0_17_0(saved)
if (semver.lt(version, '0.17.2')) migrate_0_17_2(saved)
if (semver.lt(version, '0.21.0')) migrate_0_21_0(saved)
if (semver.lt(version, '0.22.0')) migrate_0_22_0(saved)
if (semver.lt(version, '0.7.2')) {
migrate_0_7_2(state.saved)
}
if (semver.lt(version, '0.11.0')) {
migrate_0_11_0(state.saved)
if (semver.lt(version, config.APP_VERSION)) {
installHandlers(state.saved)
}
// Config is now on the new version
state.saved.version = config.APP_VERSION
}
// Whenever the app is updated, re-install default handlers if the user has
// enabled them.
function installHandlers (saved) {
if (saved.prefs.isFileHandler) {
const ipcRenderer = require('electron').ipcRenderer
ipcRenderer.send('setDefaultFileHandler', true)
}
}
function migrate_0_7_0 (saved) {
console.log('migrate to 0.7.0')
const { copyFileSync } = require('fs')
const path = require('path')
var fs = require('fs-extra')
var path = require('path')
saved.torrents.forEach(function (ts) {
var infoHash = ts.infoHash
saved.torrents.forEach(ts => {
const infoHash = ts.infoHash
// Replace torrentPath with torrentFileName
// There are a number of cases to handle here:
@@ -48,9 +61,8 @@ function migrate_0_7_0 (saved) {
// * Then, relative paths for the default torrents, eg '../static/sintel.torrent'
// * Then, paths computed at runtime for default torrents, eg 'sintel.torrent'
// * Finally, now we're getting rid of torrentPath altogether
var src, dst
let src, dst
if (ts.torrentPath) {
console.log('replacing torrentPath %s', ts.torrentPath)
if (path.isAbsolute(ts.torrentPath) || ts.torrentPath.startsWith('..')) {
src = ts.torrentPath
} else {
@@ -59,7 +71,7 @@ function migrate_0_7_0 (saved) {
dst = path.join(config.TORRENT_PATH, infoHash + '.torrent')
// Synchronous FS calls aren't ideal, but probably OK in a migration
// that only runs once
if (src !== dst) fs.copySync(src, dst)
if (src !== dst) copyFileSync(src, dst)
delete ts.torrentPath
ts.torrentFileName = infoHash + '.torrent'
@@ -67,22 +79,21 @@ function migrate_0_7_0 (saved) {
// Replace posterURL with posterFileName
if (ts.posterURL) {
console.log('replacing posterURL %s', ts.posterURL)
var extension = path.extname(ts.posterURL)
const extension = path.extname(ts.posterURL)
src = path.isAbsolute(ts.posterURL)
? ts.posterURL
: path.join(config.STATIC_PATH, ts.posterURL)
dst = path.join(config.POSTER_PATH, infoHash + extension)
// Synchronous FS calls aren't ideal, but probably OK in a migration
// that only runs once
if (src !== dst) fs.copySync(src, dst)
if (src !== dst) copyFileSync(src, dst)
delete ts.posterURL
ts.posterFileName = infoHash + extension
}
// Fix exception caused by incorrect file ordering.
// https://github.com/feross/webtorrent-desktop/pull/604#issuecomment-222805214
// https://github.com/webtorrent/webtorrent-desktop/pull/604#issuecomment-222805214
delete ts.defaultPlayFileIndex
delete ts.files
delete ts.selections
@@ -91,7 +102,7 @@ function migrate_0_7_0 (saved) {
}
function migrate_0_7_2 (saved) {
if (!saved.prefs) {
if (saved.prefs == null) {
saved.prefs = {
downloadPath: config.DEFAULT_DOWNLOAD_PATH
}
@@ -99,8 +110,125 @@ function migrate_0_7_2 (saved) {
}
function migrate_0_11_0 (saved) {
if (saved.prefs.isFileHandler === undefined) {
if (saved.prefs.isFileHandler == null) {
// The app used to make itself the default torrent file handler automatically
saved.prefs.isFileHandler = true
}
}
function migrate_0_12_0 (saved) {
const TorrentSummary = require('./torrent-summary')
if (saved.prefs.openExternalPlayer == null && saved.prefs.playInVlc != null) {
saved.prefs.openExternalPlayer = saved.prefs.playInVlc
}
delete saved.prefs.playInVlc
// Undo a terrible bug where clicking Play on a default torrent on a fresh
// install results in a "path missing" error
// See https://github.com/webtorrent/webtorrent-desktop/pull/806
const defaultTorrentFiles = [
'6a9759bffd5c0af65319979fb7832189f4f3c35d.torrent',
'88594aaacbde40ef3e2510c47374ec0aa396c08e.torrent',
'6a02592d2bbc069628cd5ed8a54f88ee06ac0ba5.torrent',
'02767050e0be2fd4db9a2ad6c12416ac806ed6ed.torrent',
'3ba219a8634bf7bae3d848192b2da75ae995589d.torrent'
]
saved.torrents.forEach(torrentSummary => {
if (!defaultTorrentFiles.includes(torrentSummary.torrentFileName)) return
const fileOrFolder = TorrentSummary.getFileOrFolder(torrentSummary)
if (!fileOrFolder) return
try {
fs.statSync(fileOrFolder)
} catch (err) {
// Default torrent with "missing path" error. Clear path.
delete torrentSummary.path
}
})
}
function migrate_0_14_0 (saved) {
saved.torrents.forEach(ts => {
delete ts.defaultPlayFileIndex
})
}
function migrate_0_17_0 (saved) {
// Fix a sad, sad bug that resulted in 100MB+ config.json files
saved.torrents.forEach(ts => {
if (!ts.files) return
ts.files.forEach(file => {
if (!file.audioInfo || !file.audioInfo.picture) return
// This contained a Buffer, which 30x'd in size when serialized to JSON
delete file.audioInfo.picture
})
})
}
function migrate_0_17_2 (saved) {
// Remove the trailing dot (.) from the Wired CD torrent name, since
// folders/files that end in a trailing dot (.) or space are not deletable from
// Windows Explorer. See: https://github.com/webtorrent/webtorrent-desktop/issues/905
const { copyFileSync } = require('fs')
const rimraf = require('rimraf')
const OLD_NAME = 'The WIRED CD - Rip. Sample. Mash. Share.'
const NEW_NAME = 'The WIRED CD - Rip. Sample. Mash. Share'
const OLD_HASH = '3ba219a8634bf7bae3d848192b2da75ae995589d'
const NEW_HASH = 'a88fda5954e89178c372716a6a78b8180ed4dad3'
const ts = saved.torrents.find(ts => ts.infoHash === OLD_HASH)
if (!ts) return // Wired CD torrent does not exist
// New versions of WebTorrent ship with a fixed torrent file. Let's fix up the
// name in existing versions of WebTorrent.
ts.name = ts.displayName = NEW_NAME
ts.files.forEach((file) => {
file.path = file.path.replace(OLD_NAME, NEW_NAME)
})
// Changing the torrent name causes the info hash to change
ts.infoHash = NEW_HASH
ts.magnetURI = ts.magnetURI.replace(OLD_HASH, NEW_HASH)
try {
fs.renameSync(
path.join(config.POSTER_PATH, ts.posterFileName),
path.join(config.POSTER_PATH, NEW_HASH + '.jpg')
)
} catch (err) {}
ts.posterFileName = NEW_HASH + '.jpg'
rimraf.sync(path.join(config.TORRENT_PATH, ts.torrentFileName))
copyFileSync(
path.join(config.STATIC_PATH, 'wiredCd.torrent'),
path.join(config.TORRENT_PATH, NEW_HASH + '.torrent')
)
ts.torrentFileName = NEW_HASH + '.torrent'
if (ts.path) {
// If torrent folder already exists on disk, try to rename it
try {
fs.renameSync(
path.join(ts.path, OLD_NAME),
path.join(ts.path, NEW_NAME)
)
} catch (err) {}
}
}
function migrate_0_21_0 (saved) {
if (saved.prefs.soundNotifications == null) {
// The app used to always have sound notifications enabled
saved.prefs.soundNotifications = true
}
}
function migrate_0_22_0 (saved) {
if (saved.prefs.externalPlayerPath == null) {
saved.prefs.externalPlayerPath = ''
}
}

View File

@@ -0,0 +1,88 @@
module.exports = {
hasNext,
getNextIndex,
hasPrevious,
getPreviousIndex,
getCurrentLocalURL
}
const TorrentSummary = require('./torrent-summary')
const TorrentPlayer = require('./torrent-player')
const cache = {
infoHash: null,
previousIndex: null,
currentIndex: null,
nextIndex: null
}
function hasNext (state) {
updateCache(state)
return cache.nextIndex !== null
}
function getNextIndex (state) {
updateCache(state)
return cache.nextIndex
}
function hasPrevious (state) {
updateCache(state)
return cache.previousIndex !== null
}
function getPreviousIndex (state) {
updateCache(state)
return cache.previousIndex
}
function getCurrentLocalURL (state) {
return state.server
? state.server.localURL + '/' + state.playing.fileIndex + '/' +
encodeURIComponent(state.playing.fileName)
: ''
}
function updateCache (state) {
const infoHash = state.playing.infoHash
const fileIndex = state.playing.fileIndex
if (infoHash === cache.infoHash) {
switch (fileIndex) {
case cache.currentIndex:
return
case cache.nextIndex:
cache.previousIndex = cache.currentIndex
cache.currentIndex = fileIndex
cache.nextIndex = findNextIndex(state)
return
case cache.previousIndex:
cache.previousIndex = findPreviousIndex(state)
cache.nextIndex = cache.currentIndex
cache.currentIndex = fileIndex
return
}
} else {
cache.infoHash = infoHash
}
cache.previousIndex = findPreviousIndex(state)
cache.currentIndex = fileIndex
cache.nextIndex = findNextIndex(state)
}
function findPreviousIndex (state) {
const files = TorrentSummary.getByKey(state, state.playing.infoHash).files
for (let i = state.playing.fileIndex - 1; i >= 0; i--) {
if (TorrentPlayer.isPlayable(files[i])) return i
}
return null
}
function findNextIndex (state) {
const files = TorrentSummary.getByKey(state, state.playing.infoHash).files
for (let i = state.playing.fileIndex + 1; i < files.length; i++) {
if (TorrentPlayer.isPlayable(files[i])) return i
}
return null
}

View File

@@ -1,28 +1,32 @@
module.exports = {
preload,
init,
play
}
var config = require('../../config')
var path = require('path')
const config = require('../../config')
const { InvalidSoundNameError } = require('./errors')
const path = require('path')
var VOLUME = 0.15
const VOLUME = 0.25
// App state to access the soundNotifications preference
let state
/* Cache of Audio elements, for instant playback */
var cache = {}
const cache = {}
var sounds = {
const sounds = {
ADD: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'add.wav'),
volume: VOLUME
},
DELETE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'delete.wav'),
volume: VOLUME
volume: VOLUME * 0.5
},
DISABLE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'disable.wav'),
volume: VOLUME
volume: VOLUME * 0.5
},
DONE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'done.wav'),
@@ -30,7 +34,7 @@ var sounds = {
},
ENABLE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'enable.wav'),
volume: VOLUME
volume: VOLUME * 0.5
},
ERROR: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'error.wav'),
@@ -42,27 +46,28 @@ var sounds = {
},
STARTUP: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'startup.wav'),
volume: VOLUME * 2
volume: VOLUME
}
}
function preload () {
for (var name in sounds) {
if (!cache[name]) {
var sound = sounds[name]
var audio = cache[name] = new window.Audio()
audio.volume = sound.volume
audio.src = sound.url
}
}
function init (appState) {
state = appState
}
function play (name) {
var audio = cache[name]
if (state == null) {
return
}
if (!state.saved.prefs.soundNotifications) {
return
}
let audio = cache[name]
if (!audio) {
var sound = sounds[name]
const sound = sounds[name]
if (!sound) {
throw new Error('Invalid sound name')
throw new InvalidSoundNameError(name)
}
audio = cache[name] = new window.Audio()
audio.volume = sound.volume

View File

@@ -1,21 +1,31 @@
var appConfig = require('application-config')('WebTorrent')
var path = require('path')
var {EventEmitter} = require('events')
const appConfig = require('application-config')('WebTorrent')
const path = require('path')
const { EventEmitter } = require('events')
var config = require('../../config')
var migrations = require('./migrations')
const config = require('../../config')
const defaultAnnounceList = require('create-torrent').announceList.map((arr) => arr[0])
var State = module.exports = Object.assign(new EventEmitter(), {
getDefaultPlayState,
load,
save,
saveThrottled
})
const SAVE_DEBOUNCE_INTERVAL = 1000
appConfig.filePath = path.join(config.CONFIG_PATH, 'config.json')
const State = module.exports = Object.assign(new EventEmitter(), {
getDefaultPlayState,
load,
// state.save() calls are rate-limited. Use state.saveImmediate() to skip limit.
save (...args) {
// Perf optimization: Lazy-require debounce (and it's dependencies)
const debounce = require('debounce')
// After first State.save() invokation, future calls go straight to the
// debounced function
State.save = debounce(saveImmediate, SAVE_DEBOUNCE_INTERVAL)
State.save(...args)
},
saveImmediate
})
function getDefaultState () {
var LocationHistory = require('location-history')
const LocationHistory = require('location-history')
return {
/*
@@ -24,7 +34,11 @@ function getDefaultState () {
*/
client: null, /* the WebTorrent client */
server: null, /* local WebTorrent-to-HTTP server */
prev: {}, /* used for state diffing in updateElectron() */
prev: { /* used for state diffing in updateElectron() */
title: null,
progress: -1,
badge: null
},
location: new LocationHistory(),
window: {
bounds: null, /* {x, y, width, height } */
@@ -64,7 +78,10 @@ function getDefaultState () {
* Getters, for convenience
*/
getPlayingTorrentSummary,
getPlayingFileSummary
getPlayingFileSummary,
getExternalPlayerName,
getGlobalTrackers,
shouldHidePlayerControls
}
}
@@ -73,10 +90,12 @@ function getDefaultPlayState () {
return {
infoHash: null, /* the info hash of the torrent we're playing */
fileIndex: null, /* the zero-based index within the torrent */
fileName: null, /* name of the file that is playing */
location: 'local', /* 'local', 'chromecast', 'airplay' */
type: null, /* 'audio' or 'video', could be 'other' if ever support eg streaming to VLC */
currentTime: 0, /* seconds */
duration: 1, /* seconds */
isReady: false,
isPaused: true,
isStalled: false,
lastTimeUpdate: 0, /* Unix time in ms */
@@ -88,52 +107,61 @@ function getDefaultPlayState () {
selectedIndex: -1, /* current subtitle track */
showMenu: false /* popover menu, above the video */
},
audioTracks: {
tracks: [],
selectedIndex: 0, /* current audio track */
showMenu: false /* popover menu, above the video */
},
aspectRatio: 0 /* aspect ratio of the video */
}
}
/* If the saved state file doesn't exist yet, here's what we use instead */
function setupSavedState (cb) {
var fs = require('fs-extra')
var parseTorrent = require('parse-torrent')
var parallel = require('run-parallel')
function setupStateSaved () {
const { copyFileSync, mkdirSync, readFileSync } = require('fs')
const parseTorrent = require('parse-torrent')
var saved = {
const saved = {
prefs: {
downloadPath: config.DEFAULT_DOWNLOAD_PATH
downloadPath: config.DEFAULT_DOWNLOAD_PATH,
isFileHandler: false,
openExternalPlayer: false,
externalPlayerPath: '',
startup: false,
soundNotifications: true,
autoAddTorrents: false,
torrentsFolderPath: '',
highestPlaybackPriority: true,
globalTrackers: defaultAnnounceList
},
torrents: config.DEFAULT_TORRENTS.map(createTorrentObject),
torrentsToResume: [],
version: config.APP_VERSION /* make sure we can upgrade gracefully later */
}
var tasks = []
// TODO: Doing several sync calls during first startup is not ideal
mkdirSync(config.POSTER_PATH, { recursive: true })
mkdirSync(config.TORRENT_PATH, { recursive: true })
config.DEFAULT_TORRENTS.map(function (t, i) {
var infoHash = saved.torrents[i].infoHash
tasks.push(function (cb) {
fs.copy(
path.join(config.STATIC_PATH, t.posterFileName),
path.join(config.POSTER_PATH, infoHash + path.extname(t.posterFileName)),
cb
)
})
tasks.push(function (cb) {
fs.copy(
path.join(config.STATIC_PATH, t.torrentFileName),
path.join(config.TORRENT_PATH, infoHash + '.torrent'),
cb
)
})
config.DEFAULT_TORRENTS.forEach((t, i) => {
const infoHash = saved.torrents[i].infoHash
// TODO: Doing several sync calls during first startup is not ideal
copyFileSync(
path.join(config.STATIC_PATH, t.posterFileName),
path.join(config.POSTER_PATH, infoHash + path.extname(t.posterFileName))
)
copyFileSync(
path.join(config.STATIC_PATH, t.torrentFileName),
path.join(config.TORRENT_PATH, infoHash + '.torrent')
)
})
parallel(tasks, function (err) {
if (err) return cb(err)
cb(null, saved)
})
return saved
function createTorrentObject (t) {
var torrent = fs.readFileSync(path.join(config.STATIC_PATH, t.torrentFileName))
var parsedTorrent = parseTorrent(torrent)
// TODO: Doing several sync calls during first startup is not ideal
const torrent = readFileSync(path.join(config.STATIC_PATH, t.torrentFileName))
const parsedTorrent = parseTorrent(torrent)
return {
status: 'paused',
@@ -144,62 +172,92 @@ function setupSavedState (cb) {
torrentFileName: parsedTorrent.infoHash + '.torrent',
magnetURI: parseTorrent.toMagnetURI(parsedTorrent),
files: parsedTorrent.files,
selections: parsedTorrent.files.map((x) => true)
selections: parsedTorrent.files.map((x) => true),
testID: t.testID
}
}
}
function getPlayingTorrentSummary () {
var infoHash = this.playing.infoHash
const infoHash = this.playing.infoHash
return this.saved.torrents.find((x) => x.infoHash === infoHash)
}
function getPlayingFileSummary () {
var torrentSummary = this.getPlayingTorrentSummary()
const torrentSummary = this.getPlayingTorrentSummary()
if (!torrentSummary) return null
return torrentSummary.files[this.playing.fileIndex]
}
function load (cb) {
var state = getDefaultState()
function getExternalPlayerName () {
const playerPath = this.saved.prefs.externalPlayerPath
if (!playerPath) return 'VLC'
return path.basename(playerPath).split('.')[0]
}
appConfig.read(function (err, saved) {
if (err || !saved.version) {
console.log('Missing config file: Creating new one')
setupSavedState(onSaved)
} else {
onSaved(null, saved)
function shouldHidePlayerControls () {
return this.location.url() === 'player' &&
this.playing.mouseStationarySince !== 0 &&
new Date().getTime() - this.playing.mouseStationarySince > 2000 &&
!this.playing.mouseInControls &&
!this.playing.isPaused &&
this.playing.location === 'local'
}
function getGlobalTrackers () {
const trackers = this.saved.prefs.globalTrackers
if (!trackers) {
return defaultAnnounceList
}
return trackers
}
async function load (cb) {
let saved = await appConfig.read()
if (!saved || !saved.version) {
console.log('Missing config file: Creating new one')
try {
saved = setupStateSaved()
} catch (err) {
onSavedState(err)
return
}
})
}
function onSaved (err, saved) {
onSavedState(null, saved)
function onSavedState (err, saved) {
if (err) return cb(err)
const state = getDefaultState()
state.saved = saved
migrations.run(state)
if (process.type === 'renderer') {
// Perf optimization: Save require() calls in the main process
const migrations = require('./migrations')
migrations.run(state)
}
cb(null, state)
}
}
// Write state.saved to the JSON state file
function save (state, cb) {
async function saveImmediate (state, cb) {
console.log('Saving state to ' + appConfig.filePath)
delete state.saveStateTimeout
// Clean up, so that we're not saving any pending state
var copy = Object.assign({}, state.saved)
const copy = Object.assign({}, state.saved)
// Remove torrents pending addition to the list, where we haven't finished
// reading the torrent file or file(s) to seed & don't have an infohash
copy.torrents = copy.torrents
.filter((x) => x.infoHash)
.map(function (x) {
var torrent = {}
for (var key in x) {
.map(x => {
const torrent = {}
for (const key in x) {
if (key === 'progress' || key === 'torrentKey') {
continue // Don't save progress info or key for the webtorrent process
}
if (key === 'playStatus') {
continue // Don't save whether a torrent is playing / pending
}
if (key === 'error') {
continue // Don't save error states
}
@@ -208,17 +266,10 @@ function save (state, cb) {
return torrent
})
appConfig.write(copy, (err) => {
if (err) console.error(err)
else State.emit('savedState')
})
}
// Write, but no more than once a second
function saveThrottled (state) {
if (state.saveStateTimeout) return
state.saveStateTimeout = setTimeout(function () {
if (!state.saveStateTimeout) return
save(state)
}, 1000)
try {
await appConfig.write(copy)
State.emit('stateSaved')
} catch (err) {
console.error(err)
}
}

View File

@@ -2,92 +2,79 @@
// Reports back so that we can improve WebTorrent Desktop
module.exports = {
init,
send,
logUncaughtError,
logPlayAttempt
}
const crypto = require('crypto')
const electron = require('electron')
const https = require('https')
const os = require('os')
const url = require('url')
const remote = require('@electron/remote')
const config = require('../../config')
var telemetry
let telemetry
function init (state) {
telemetry = state.saved.telemetry
// First app run
if (!telemetry) {
telemetry = state.saved.telemetry = createSummary()
const crypto = require('crypto')
telemetry = state.saved.telemetry = {
userID: crypto.randomBytes(32).toString('hex') // 256-bit random ID
}
reset()
}
}
var now = new Date()
function send (state) {
const now = new Date()
telemetry.version = config.APP_VERSION
telemetry.timestamp = now.toISOString()
telemetry.localTime = now.toTimeString()
telemetry.screens = getScreenInfo()
telemetry.system = getSystemInfo()
telemetry.approxNumTorrents = getApproxNumTorrents(state)
telemetry.torrentStats = getTorrentStats(state)
telemetry.approxNumTorrents = telemetry.torrentStats.approxCount
if (config.IS_PRODUCTION) {
postToServer()
} else {
if (!config.IS_PRODUCTION) {
// Development: telemetry used only for local debugging
// Empty uncaught errors, etc at the start of every run
reset()
return reset()
}
const get = require('simple-get')
const opts = {
url: config.TELEMETRY_URL,
body: telemetry,
json: true
}
get.post(opts, (err, res) => {
if (err) return console.error('Error sending telemetry', err)
if (res.statusCode !== 200) {
return console.error(`Error sending telemetry, status code: ${res.statusCode}`)
}
console.log('Sent telemetry')
reset()
})
}
function reset () {
telemetry.uncaughtErrors = []
telemetry.playAttempts = {
minVersion: config.APP_VERSION,
total: 0,
success: 0,
timeout: 0,
error: 0,
external: 0,
abandoned: 0
}
}
function postToServer () {
// Serialize the telemetry summary
var payload = new Buffer(JSON.stringify(telemetry), 'utf8')
// POST to our server
var options = url.parse(config.TELEMETRY_URL)
options.method = 'POST'
options.headers = {
'Content-Type': 'application/json',
'Content-Length': payload.length
}
var req = https.request(options, function (res) {
if (res.statusCode === 200) {
console.log('Successfully posted telemetry summary')
reset()
} else {
console.error('Couldn\'t post telemetry summary, got HTTP ' + res.statusCode)
}
})
req.on('error', function (e) {
console.error('Couldn\'t post telemetry summary', e)
})
req.write(payload)
req.end()
}
// Creates a new telemetry summary. Gives the user a unique ID,
// collects screen resolution, etc
function createSummary () {
// Make a 256-bit random unique ID
var userID = crypto.randomBytes(32).toString('hex')
return { userID }
}
// Track screen resolution
function getScreenInfo () {
return electron.screen.getAllDisplays().map((screen) => ({
return remote.screen.getAllDisplays().map((screen) => ({
width: screen.size.width,
height: screen.size.height,
scaleFactor: screen.scaleFactor
@@ -96,57 +83,140 @@ function getScreenInfo () {
// Track basic system info like OS version and amount of RAM
function getSystemInfo () {
const os = require('os')
return {
osPlatform: process.platform,
osRelease: os.type() + ' ' + os.release(),
architecture: os.arch(),
totalMemoryMB: os.totalmem() / (1 << 20),
systemArchitecture: config.OS_SYSARCH,
totalMemoryMB: roundPow2(os.totalmem() / (1 << 20)),
numCores: os.cpus().length
}
}
// Get the number of torrents, rounded to the nearest power of two
function getApproxNumTorrents (state) {
var exactNum = state.saved.torrents.length
if (exactNum === 0) return 0
// Get stats like the # of torrents currently active, # in list, total size
function getTorrentStats (state) {
const count = state.saved.torrents.length
let sizeMB = 0
const byStatus = {
new: { count: 0, sizeMB: 0 },
downloading: { count: 0, sizeMB: 0 },
seeding: { count: 0, sizeMB: 0 },
paused: { count: 0, sizeMB: 0 }
}
// First, count torrents & total file size
for (const torrent of state.saved.torrents) {
const stat = byStatus[torrent.status]
if (!torrent || !torrent.files || !stat) continue
stat.count++
for (const file of torrent.files) {
if (!file || !file.length) continue
const fileSizeMB = file.length / (1 << 20)
sizeMB += fileSizeMB
stat.sizeMB += fileSizeMB
}
}
// Then, round all the counts and sums to the nearest power of 2
const ret = roundTorrentStats({ count, sizeMB })
ret.byStatus = {
new: roundTorrentStats(byStatus.new),
downloading: roundTorrentStats(byStatus.downloading),
seeding: roundTorrentStats(byStatus.seeding),
paused: roundTorrentStats(byStatus.paused)
}
return ret
}
function roundTorrentStats (stats) {
return {
approxCount: roundPow2(stats.count),
approxSizeMB: roundPow2(stats.sizeMB)
}
}
// Rounds to the nearest power of 2, for privacy and easy bucketing.
// Rounds 35 to 32, 70 to 64, 5 to 4, 1 to 1, 0 to 0.
// Supports nonnegative numbers only.
function roundPow2 (n) {
if (n <= 0) return 0
// Otherwise, return 1, 2, 4, 8, etc by rounding in log space
var log2 = Math.log(exactNum) / Math.log(2)
return 1 << Math.round(log2)
const log2 = Math.log(n) / Math.log(2)
return 2 ** Math.round(log2)
}
// An uncaught error happened in the main process or in one of the windows
function logUncaughtError (procName, err) {
console.error('uncaught error', procName, err)
function logUncaughtError (procName, e) {
// Not initialized yet? Ignore.
// Hopefully uncaught errors immediately on startup are fixed in dev
if (!telemetry) return
var message, stack
if (err instanceof Error) {
message = err.message
stack = err.stack
let message
let stack = ''
if (e == null) {
message = 'Unexpected undefined error'
} else if (e.error) {
// Uncaught Javascript errors (window.onerror), err is an ErrorEvent
if (!e.error.message) {
message = 'Unexpected ErrorEvent.error: ' + Object.keys(e.error).join(' ')
} else {
message = e.error.message
stack = e.error.stack
}
} else if (e.message) {
// err is either an Error or a plain object {message, stack}
message = e.message
stack = e.stack
} else {
message = String(err)
stack = ''
// Resource errors (captured element.onerror), err is an Event
if (!e.target) {
message = 'Unexpected unknown error'
} else if (!e.target.error) {
message = 'Unexpected resource loading error: ' + getElemString(e.target)
} else {
message = 'Resource error ' + getElemString(e.target) + ': ' + e.target.error.code
}
}
if (typeof stack !== 'string') stack = 'Unexpected stack: ' + stack
if (typeof message !== 'string') message = 'Unexpected message: ' + message
// Remove the first part of each file path in the stack trace.
// - Privacy: remove personal info like C:\Users\<full name>
// - Aggregation: this lets us find which stacktraces occur often
stack = stack.replace(/\(.*app.asar/g, '(...')
stack = stack.replace(/at .*app.asar/g, 'at ...')
// We need to POST the telemetry object, make sure it stays < 100kb
if (telemetry.uncaughtErrors.length > 20) return
if (message.length > 1000) message = message.substring(0, 1000)
if (stack.length > 1000) stack = stack.substring(0, 1000)
telemetry.uncaughtErrors.push({process: procName, message, stack})
// Log the app version *at the time of the error*
const version = config.APP_VERSION
telemetry.uncaughtErrors.push({ process: procName, message, stack, version })
}
// The user pressed play. It either worked, timed out, or showed the
// 'Play in VLC' codec error
// Turns a DOM element into a string, eg "DIV.my-class.visible"
function getElemString (elem) {
let ret = elem.tagName
try {
ret += '.' + Array.from(elem.classList).join('.')
} catch (err) {}
return ret
}
// The user pressed play. Did it work, display an error,
// open an external player or did user abandon the attempt?
function logPlayAttempt (result) {
if (!['success', 'timeout', 'error', 'abandoned'].includes(result)) {
if (!['success', 'error', 'external', 'abandoned'].includes(result)) {
return console.error('Unknown play attempt result', result)
}
var attempts = telemetry.playAttempts
const attempts = telemetry.playAttempts
attempts.total = (attempts.total || 0) + 1
attempts[result] = (attempts[result] || 0) + 1
}

20
src/renderer/lib/time.js Normal file
View File

@@ -0,0 +1,20 @@
module.exports = {
calculateEta
}
function calculateEta (missing, downloadSpeed) {
if (downloadSpeed === 0 || missing === 0) return
const rawEta = missing / downloadSpeed
const hours = Math.floor(rawEta / 3600) % 24
const minutes = Math.floor(rawEta / 60) % 60
const seconds = Math.floor(rawEta % 60)
// Only display hours and minutes if they are greater than 0 but always
// display minutes if hours is being displayed
const hoursStr = hours ? hours + ' h' : ''
const minutesStr = (hours || minutes) ? minutes + ' min' : ''
const secondsStr = seconds + ' s'
return `${hoursStr} ${minutesStr} ${secondsStr} remaining`
}

View File

@@ -3,11 +3,13 @@ module.exports = {
isVideo,
isAudio,
isTorrent,
isPlayableTorrentSummary,
pickFileToPlay
isMagnetLink,
isPlayableTorrentSummary
}
var path = require('path')
const path = require('path')
const mediaExtensions = require('./media-extensions')
// Checks whether a fileSummary or file path is audio/video that we can play,
// based on the file extension
@@ -17,28 +19,12 @@ function isPlayable (file) {
// Checks whether a fileSummary or file path is playable video
function isVideo (file) {
return [
'.avi',
'.m4v',
'.mkv',
'.mov',
'.mp4',
'.mpg',
'.ogv',
'.webm',
'.wmv'
].includes(getFileExtension(file))
return mediaExtensions.video.includes(getFileExtension(file))
}
// Checks whether a fileSummary or file path is playable audio
function isAudio (file) {
return [
'.aac',
'.ac3',
'.mp3',
'.ogg',
'.wav'
].includes(getFileExtension(file))
return mediaExtensions.audio.includes(getFileExtension(file))
}
// Checks if the argument is either:
@@ -46,38 +32,22 @@ function isAudio (file) {
// - a file object where obj.name is ends in .torrent
// - a string that's a magnet link (magnet://...)
function isTorrent (file) {
var isTorrentFile = getFileExtension(file) === '.torrent'
var isMagnet = typeof file === 'string' && /^(stream-)?magnet:/.test(file)
return isTorrentFile || isMagnet
return isTorrentFile(file) || isMagnetLink(file)
}
function isTorrentFile (file) {
return getFileExtension(file) === '.torrent'
}
function isMagnetLink (link) {
return typeof link === 'string' && /^(stream-)?magnet:/.test(link)
}
function getFileExtension (file) {
var name = typeof file === 'string' ? file : file.name
const name = typeof file === 'string' ? file : file.name
return path.extname(name).toLowerCase()
}
function isPlayableTorrentSummary (torrentSummary) {
return torrentSummary.files && torrentSummary.files.some(isPlayable)
}
// Picks the default file to play from a list of torrent or torrentSummary files
// Returns an index or undefined, if no files are playable
function pickFileToPlay (files) {
// first, try to find the biggest video file
var videoFiles = files.filter(isVideo)
if (videoFiles.length > 0) {
var largestVideoFile = videoFiles.reduce(function (a, b) {
return a.length > b.length ? a : b
})
return files.indexOf(largestVideoFile)
}
// if there are no videos, play the first audio file
var audioFiles = files.filter(isAudio)
if (audioFiles.length > 0) {
return files.indexOf(audioFiles[0])
}
// no video or audio means nothing is playable
return undefined
}

View File

@@ -1,50 +1,143 @@
module.exports = torrentPoster
var captureVideoFrame = require('./capture-video-frame')
var path = require('path')
const captureFrame = require('capture-frame')
const path = require('path')
const mediaExtensions = require('./media-extensions')
const msgNoSuitablePoster = 'Cannot generate a poster from any files in the torrent'
function torrentPoster (torrent, cb) {
// First, try to use a poster image if available
var posterFile = torrent.files.filter(function (file) {
return /^poster\.(jpg|png|gif)$/.test(file.name)
})[0]
if (posterFile) return torrentPosterFromImage(posterFile, torrent, cb)
const posterFile = torrent.files.filter(file => /^poster\.(jpg|png|gif)$/.test(file.name))[0]
if (posterFile) return extractPoster(posterFile, cb)
// Second, try to use the largest video file
// Filter out file formats that the <video> tag definitely can't play
var videoFile = getLargestFileByExtension(torrent, ['.mp4', '.m4v', '.webm', '.mov', '.mkv'])
if (videoFile) return torrentPosterFromVideo(videoFile, torrent, cb)
// 'score' each media type based on total size present in torrent
const bestScore = ['audio', 'video', 'image'].map(mediaType => ({
type: mediaType,
size: calculateDataLengthByExtension(torrent, mediaExtensions[mediaType])
})).sort((a, b) => b.size - a.size)[0] // sort descending on size
// Third, try to use the largest image file
var imgFile = getLargestFileByExtension(torrent, ['.gif', '.jpg', '.jpeg', '.png'])
if (imgFile) return torrentPosterFromImage(imgFile, torrent, cb)
if (bestScore.size === 0) {
// Admit defeat, no video, audio or image had a significant presence
return cb(new Error(msgNoSuitablePoster))
}
// TODO: generate a waveform from the largest sound file
// Finally, admit defeat
return cb(new Error('Cannot generate a poster from any files in the torrent'))
// Based on which media type is dominant we select the corresponding poster function
switch (bestScore.type) {
case 'audio':
return torrentPosterFromAudio(torrent, cb)
case 'image':
return torrentPosterFromImage(torrent, cb)
case 'video':
return torrentPosterFromVideo(torrent, cb)
}
}
/**
* Calculate the total data size of file matching one of the provided extensions
* @param torrent
* @param extensions List of extension to match
* @returns {number} total size, of matches found (>= 0)
*/
function calculateDataLengthByExtension (torrent, extensions) {
const files = filterOnExtension(torrent, extensions)
if (files.length === 0) return 0
return files
.map(file => file.length)
.reduce((a, b) => a + b)
}
/**
* Get the largest file of a given torrent, filtered by provided extension
* @param torrent Torrent to search in
* @param extensions Extension whitelist filter
* @returns Torrent file object
*/
function getLargestFileByExtension (torrent, extensions) {
var files = torrent.files.filter(function (file) {
var extname = path.extname(file.name).toLowerCase()
const files = filterOnExtension(torrent, extensions)
if (files.length === 0) return undefined
return files.reduce((a, b) => a.length > b.length ? a : b)
}
/**
* Filter file on a list extension, can be used to find al image files
* @param torrent Torrent to filter files from
* @param extensions File extensions to filter on
* @returns {Array} Array of torrent file objects matching one of the given extensions
*/
function filterOnExtension (torrent, extensions) {
return torrent.files.filter(file => {
const extname = path.extname(file.name).toLowerCase()
return extensions.indexOf(extname) !== -1
})
if (files.length === 0) return undefined
return files.reduce(function (a, b) {
return a.length > b.length ? a : b
})
}
function torrentPosterFromVideo (file, torrent, cb) {
var index = torrent.files.indexOf(file)
/**
* Returns a score how likely the file is suitable as a poster
* @param imgFile File object of an image
* @returns {number} Score, higher score is a better match
*/
function scoreAudioCoverFile (imgFile) {
const fileName = path.basename(imgFile.name, path.extname(imgFile.name)).toLowerCase()
const relevanceScore = {
cover: 80,
folder: 80,
album: 80,
front: 80,
back: 20,
spectrogram: -80
}
var server = torrent.createServer(0)
for (const keyword in relevanceScore) {
if (fileName === keyword) {
return relevanceScore[keyword]
}
if (fileName.indexOf(keyword) !== -1) {
return relevanceScore[keyword]
}
}
return 0
}
function torrentPosterFromAudio (torrent, cb) {
const imageFiles = filterOnExtension(torrent, mediaExtensions.image)
if (imageFiles.length === 0) return cb(new Error(msgNoSuitablePoster))
const bestCover = imageFiles.map(file => ({
file,
score: scoreAudioCoverFile(file)
})).reduce((a, b) => {
if (a.score > b.score) {
return a
}
if (b.score > a.score) {
return b
}
// If score is equal, pick the largest file, aiming for highest resolution
if (a.file.length > b.file.length) {
return a
}
return b
})
const extname = path.extname(bestCover.file.name)
bestCover.file.getBuffer((err, buf) => cb(err, buf, extname))
}
function torrentPosterFromVideo (torrent, cb) {
const file = getLargestFileByExtension(torrent, mediaExtensions.video)
const index = torrent.files.indexOf(file)
const server = torrent.createServer(0)
server.listen(0, onListening)
function onListening () {
var port = server.address().port
var url = 'http://localhost:' + port + '/' + index
var video = document.createElement('video')
const port = server.address().port
const url = 'http://localhost:' + port + '/' + index
const video = document.createElement('video')
video.addEventListener('canplay', onCanPlay)
video.volume = 0
@@ -61,7 +154,8 @@ function torrentPosterFromVideo (file, torrent, cb) {
function onSeeked () {
video.removeEventListener('seeked', onSeeked)
var buf = captureVideoFrame(video)
const frame = captureFrame(video)
const buf = frame && frame.image
// unload video element
video.pause()
@@ -70,14 +164,19 @@ function torrentPosterFromVideo (file, torrent, cb) {
server.destroy()
if (buf.length === 0) return cb(new Error('Generated poster contains no data'))
if (buf.length === 0) return cb(new Error(msgNoSuitablePoster))
cb(null, buf, '.jpg')
}
}
}
function torrentPosterFromImage (file, torrent, cb) {
var extname = path.extname(file.name)
function torrentPosterFromImage (torrent, cb) {
const file = getLargestFileByExtension(torrent, mediaExtensions.image)
extractPoster(file, cb)
}
function extractPoster (file, cb) {
const extname = path.extname(file.name)
file.getBuffer((err, buf) => cb(err, buf, extname))
}

View File

@@ -2,12 +2,12 @@ module.exports = {
getPosterPath,
getTorrentPath,
getByKey,
getTorrentID,
getTorrentId,
getFileOrFolder
}
var path = require('path')
var config = require('../../config')
const path = require('path')
const config = require('../../config')
// Expects a torrentSummary
// Returns an absolute path to the torrent file, or null if unavailable
@@ -20,7 +20,7 @@ function getTorrentPath (torrentSummary) {
// Returns an absolute path to the poster image, or null if unavailable
function getPosterPath (torrentSummary) {
if (!torrentSummary || !torrentSummary.posterFileName) return null
var posterPath = path.join(config.POSTER_PATH, torrentSummary.posterFileName)
const posterPath = path.join(config.POSTER_PATH, torrentSummary.posterFileName)
// Work around a Chrome bug (reproduced in vanilla Chrome, not just Electron):
// Backslashes in URLS in CSS cause bizarre string encoding issues
return posterPath.replace(/\\/g, '/')
@@ -28,8 +28,8 @@ function getPosterPath (torrentSummary) {
// Expects a torrentSummary
// Returns a torrentID: filename, magnet URI, or infohash
function getTorrentID (torrentSummary) {
var s = torrentSummary
function getTorrentId (torrentSummary) {
const s = torrentSummary
if (s.torrentFileName) { // Load torrent file from disk
return getTorrentPath(s)
} else { // Load torrent from DHT
@@ -51,6 +51,8 @@ function getByKey (state, torrentKey) {
// TODO: make this assumption explicit, enforce it in the `create-torrent`
// module. Store root folder explicitly to avoid hacky path processing below.
function getFileOrFolder (torrentSummary) {
var ts = torrentSummary
return path.join(ts.path, ts.files[0].path.split('/')[0])
const ts = torrentSummary
if (!ts.path || !ts.files || ts.files.length === 0) return null
const dirname = ts.files[0].path.split(path.sep)[0]
return path.join(ts.path, dirname)
}

View File

@@ -1,69 +1,118 @@
/**
* Perf optimization: Hook into require() to modify how certain modules load:
*
* - `inline-style-prefixer` (used by `material-ui`) takes ~40ms. It is not
* actually used because auto-prefixing is disabled with
* `darkBaseTheme.userAgent = false`. Return a fake object.
*/
const Module = require('module')
const _require = Module.prototype.require
Module.prototype.require = function (id) {
if (id === 'inline-style-prefixer') return {}
return _require.apply(this, arguments)
}
console.time('init')
const crashReporter = require('../crash-reporter')
crashReporter.init()
// Perf optimization: Start asynchronously read on config file before all the
// blocking require() calls below.
const State = require('./lib/state')
State.load(onState)
const createGetter = require('fn-getter')
const debounce = require('debounce')
const dragDrop = require('drag-drop')
const electron = require('electron')
const fs = require('fs')
const React = require('react')
const ReactDOM = require('react-dom')
const fs = require('fs')
const config = require('../config')
const App = require('./views/app')
const telemetry = require('./lib/telemetry')
const sound = require('./lib/sound')
const State = require('./lib/state')
const TorrentPlayer = require('./lib/torrent-player')
const MediaController = require('./controllers/media-controller')
const UpdateController = require('./controllers/update-controller')
const PrefsController = require('./controllers/prefs-controller')
const PlaybackController = require('./controllers/playback-controller')
const SubtitlesController = require('./controllers/subtitles-controller')
// Perf optimization: Needed immediately, so do not lazy load it below
const TorrentListController = require('./controllers/torrent-list-controller')
const TorrentController = require('./controllers/torrent-controller')
const App = require('./pages/app')
// Electron apps have two processes: a main process (node) runs first and starts
// a renderer process (essentially a Chrome window). We're in the renderer process,
// and this IPC channel receives from and sends messages to the main process
const ipcRenderer = electron.ipcRenderer
// Yo-yo pattern: state object lives here and percolates down thru all the views.
// Events come back up from the views via dispatch(...)
require('./lib/dispatcher').setDispatch(dispatch)
// From dispatch(...), events are sent to one of the controllers
var controllers = null
let controllers = null
// This dependency is the slowest-loading, so we lazy load it
var Cast = null
// Electron apps have two processes: a main process (node) runs first and starts
// a renderer process (essentially a Chrome window). We're in the renderer process,
// and this IPC channel receives from and sends messages to the main process
var ipcRenderer = electron.ipcRenderer
let Cast = null
// All state lives in state.js. `state.saved` is read from and written to a file.
// All other state is ephemeral. First we load state.saved then initialize the app.
var state
let state
// Root React component
var app
State.load(onState)
let app
// Called once when the application loads. (Not once per window.)
// Connects to the torrent networks, sets up the UI and OS integrations like
// the dock icon and drag+drop.
function onState (err, _state) {
if (err) return onError(err)
state = window.state = _state // Make available for easier debugging
// Make available for easier debugging
state = window.state = _state
window.dispatch = dispatch
telemetry.init(state)
sound.init(state)
// Log uncaught JS errors
window.addEventListener(
'error', (e) => telemetry.logUncaughtError('window', e), true /* capture */
)
// Create controllers
controllers = {
media: new MediaController(state),
update: new UpdateController(state),
prefs: new PrefsController(state, config),
playback: new PlaybackController(state, config, update),
subtitles: new SubtitlesController(state),
torrentList: new TorrentListController(state),
torrent: new TorrentController(state)
media: createGetter(() => {
const MediaController = require('./controllers/media-controller')
return new MediaController(state)
}),
playback: createGetter(() => {
const PlaybackController = require('./controllers/playback-controller')
return new PlaybackController(state, config, update)
}),
prefs: createGetter(() => {
const PrefsController = require('./controllers/prefs-controller')
return new PrefsController(state, config)
}),
subtitles: createGetter(() => {
const SubtitlesController = require('./controllers/subtitles-controller')
return new SubtitlesController(state)
}),
audioTracks: createGetter(() => {
const AudioTracksController = require('./controllers/audio-tracks-controller')
return new AudioTracksController(state)
}),
torrent: createGetter(() => {
const TorrentController = require('./controllers/torrent-controller')
return new TorrentController(state)
}),
torrentList: createGetter(() => new TorrentListController(state)),
update: createGetter(() => {
const UpdateController = require('./controllers/update-controller')
return new UpdateController(state)
}),
folderWatcher: createGetter(() => {
const FolderWatcherController = require('./controllers/folder-watcher-controller')
return new FolderWatcherController()
})
}
// Add first page to location history
@@ -75,26 +124,27 @@ function onState (err, _state) {
}
})
// Give global trackers
setGlobalTrackers()
// Restart everything we were torrenting last time the app ran
resumeTorrents()
// Initialize ReactDOM
ReactDOM.render(
<App state={state} ref={elem => { app = elem }} />,
document.querySelector('#body')
)
// Calling update() updates the UI given the current state
// Do this at least once a second to give every file in every torrentSummary
// a progress bar and to keep the cursor in sync when playing a video
setInterval(update, 1000)
app = ReactDOM.render(<App state={state} />, document.querySelector('#body'))
// Lazy-load other stuff, like the AppleTV module, later to keep startup fast
window.setTimeout(delayedInit, config.DELAYED_INIT)
// Listen for messages from the main process
setupIpc()
// Warn if the download dir is gone, eg b/c an external drive is unplugged
checkDownloadPath()
// OS integrations:
// ...drag and drop files/text to start torrenting or seeding
// Drag and drop files/text to start torrenting or seeding
dragDrop('body', {
onDrop: onOpen,
onDropText: onOpen
@@ -103,27 +153,52 @@ function onState (err, _state) {
// ...same thing if you paste a torrent
document.addEventListener('paste', onPaste)
// Add YouTube style hotkey shortcuts
window.addEventListener('keydown', onKeydown)
const debouncedFullscreenToggle = debounce(() => {
dispatch('toggleFullScreen')
}, 1000, true)
document.addEventListener('wheel', event => {
// ctrlKey detects pinch to zoom, http://crbug.com/289887
if (event.ctrlKey) {
event.preventDefault()
debouncedFullscreenToggle()
}
})
// ...focus and blur. Needed to show correct dock icon text ('badge') in OSX
window.addEventListener('focus', onFocus)
window.addEventListener('blur', onBlur)
// ...window visibility state.
document.addEventListener('webkitvisibilitychange', onVisibilityChange)
if (electron.remote.getCurrentWindow().isVisible()) {
sound.play('STARTUP')
}
// Log uncaught JS errors
window.addEventListener('error',
(e) => telemetry.logUncaughtError('window', e.error || e.target), true)
// To keep app startup fast, some code is delayed.
window.setTimeout(delayedInit, config.DELAYED_INIT)
// Done! Ideally we want to get here < 500ms after the user clicks the app
sound.play('STARTUP')
console.timeEnd('init')
}
// Runs a few seconds after the app loads, to avoid slowing down startup time
function delayedInit () {
telemetry.send(state)
// Send telemetry data every 12 hours, for users who keep the app running
// for extended periods of time
setInterval(() => telemetry.send(state), 12 * 3600 * 1000)
// Warn if the download dir is gone, eg b/c an external drive is unplugged
checkDownloadPath()
// ...window visibility state.
document.addEventListener('webkitvisibilitychange', onVisibilityChange)
onVisibilityChange()
lazyLoadCast()
sound.preload()
telemetry.init(state)
}
// Lazily loads Chromecast and Airplay support
@@ -141,7 +216,7 @@ function lazyLoadCast () {
// 3. dispatch - the event handler calls dispatch(), main.js sends it to a controller
// 4. controller - the controller handles the event, changing the state object
function update () {
controllers.playback.showOrHidePlayerControls()
controllers.playback().showOrHidePlayerControls()
app.setState(state)
updateElectron()
}
@@ -153,7 +228,7 @@ function updateElectron () {
state.prev.title = state.window.title
ipcRenderer.send('setTitle', state.window.title)
}
if (state.dock.progress !== state.prev.progress) {
if (state.dock.progress.toFixed(2) !== state.prev.progress.toFixed(2)) {
state.prev.progress = state.dock.progress
ipcRenderer.send('setProgress', state.dock.progress)
}
@@ -165,83 +240,112 @@ function updateElectron () {
const dispatchHandlers = {
// Torrent list: creating, deleting, selecting torrents
'openTorrentFile': () => ipcRenderer.send('openTorrentFile'),
'openFiles': () => ipcRenderer.send('openFiles'), /* shows the open file dialog */
'openTorrentAddress': () => { state.modal = { id: 'open-torrent-address-modal' } },
openTorrentFile: () => ipcRenderer.send('openTorrentFile'),
openFiles: () => ipcRenderer.send('openFiles'), /* shows the open file dialog */
openTorrentAddress: () => { state.modal = { id: 'open-torrent-address-modal' } },
'addTorrent': (torrentId) => controllers.torrentList.addTorrent(torrentId),
'showCreateTorrent': (paths) => controllers.torrentList.showCreateTorrent(paths),
'toggleCreateTorrentAdvanced': () => controllers.torrentList.toggleCreateTorrentAdvanced(),
'createTorrent': (options) => controllers.torrentList.createTorrent(options),
'toggleTorrent': (infoHash) => controllers.torrentList.toggleTorrent(infoHash),
'toggleTorrentFile': (infoHash, index) => controllers.torrentList.toggleTorrentFile(infoHash, index),
'confirmDeleteTorrent': (infoHash, deleteData) => controllers.torrentList.confirmDeleteTorrent(infoHash, deleteData),
'deleteTorrent': (infoHash, deleteData) => controllers.torrentList.deleteTorrent(infoHash, deleteData),
'toggleSelectTorrent': (infoHash) => controllers.torrentList.toggleSelectTorrent(infoHash),
'openTorrentContextMenu': (infoHash) => controllers.torrentList.openTorrentContextMenu(infoHash),
'startTorrentingSummary': (torrentKey) => controllers.torrentList.startTorrentingSummary(torrentKey),
addTorrent: (torrentId) => controllers.torrentList().addTorrent(torrentId),
showCreateTorrent: (paths) => controllers.torrentList().showCreateTorrent(paths),
createTorrent: (options) => controllers.torrentList().createTorrent(options),
toggleTorrent: (infoHash) => controllers.torrentList().toggleTorrent(infoHash),
pauseAllTorrents: () => controllers.torrentList().pauseAllTorrents(),
resumeAllTorrents: () => controllers.torrentList().resumeAllTorrents(),
toggleTorrentFile: (infoHash, index) =>
controllers.torrentList().toggleTorrentFile(infoHash, index),
confirmDeleteTorrent: (infoHash, deleteData) =>
controllers.torrentList().confirmDeleteTorrent(infoHash, deleteData),
deleteTorrent: (infoHash, deleteData) =>
controllers.torrentList().deleteTorrent(infoHash, deleteData),
openTorrentListContextMenu: () => onPaste(),
confirmDeleteAllTorrents: (deleteData) =>
controllers.torrentList().confirmDeleteAllTorrents(deleteData),
deleteAllTorrents: (deleteData) =>
controllers.torrentList().deleteAllTorrents(deleteData),
toggleSelectTorrent: (infoHash) =>
controllers.torrentList().toggleSelectTorrent(infoHash),
openTorrentContextMenu: (infoHash) =>
controllers.torrentList().openTorrentContextMenu(infoHash),
startTorrentingSummary: (torrentKey) =>
controllers.torrentList().startTorrentingSummary(torrentKey),
saveTorrentFileAs: (torrentKey) =>
controllers.torrentList().saveTorrentFileAs(torrentKey),
prioritizeTorrent: (infoHash) => controllers.torrentList().prioritizeTorrent(infoHash),
resumePausedTorrents: () => controllers.torrentList().resumePausedTorrents(),
// Playback
'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index),
'playPause': () => controllers.playback.playPause(),
'skip': (time) => controllers.playback.skip(time),
'skipTo': (time) => controllers.playback.skipTo(time),
'changePlaybackRate': (dir) => controllers.playback.changePlaybackRate(dir),
'changeVolume': (delta) => controllers.playback.changeVolume(delta),
'setVolume': (vol) => controllers.playback.setVolume(vol),
'openItem': (infoHash, index) => controllers.playback.openItem(infoHash, index),
playFile: (infoHash, index) => controllers.playback().playFile(infoHash, index),
playPause: () => controllers.playback().playPause(),
nextTrack: () => controllers.playback().nextTrack(),
previousTrack: () => controllers.playback().previousTrack(),
skip: (time) => controllers.playback().skip(time),
skipTo: (time) => controllers.playback().skipTo(time),
preview: (x) => controllers.playback().preview(x),
clearPreview: () => controllers.playback().clearPreview(),
changePlaybackRate: (dir) => controllers.playback().changePlaybackRate(dir),
changeVolume: (delta) => controllers.playback().changeVolume(delta),
setVolume: (vol) => controllers.playback().setVolume(vol),
openPath: (infoHash, index) => controllers.playback().openPath(infoHash, index),
// Subtitles
'openSubtitles': () => controllers.subtitles.openSubtitles(),
'selectSubtitle': (index) => controllers.subtitles.selectSubtitle(index),
'toggleSubtitlesMenu': () => controllers.subtitles.toggleSubtitlesMenu(),
'checkForSubtitles': () => controllers.subtitles.checkForSubtitles(),
'addSubtitles': (files, autoSelect) => controllers.subtitles.addSubtitles(files, autoSelect),
openSubtitles: () => controllers.subtitles().openSubtitles(),
selectSubtitle: (index) => controllers.subtitles().selectSubtitle(index),
toggleSubtitlesMenu: () => controllers.subtitles().toggleSubtitlesMenu(),
checkForSubtitles: () => controllers.subtitles().checkForSubtitles(),
addSubtitles: (files, autoSelect) => controllers.subtitles().addSubtitles(files, autoSelect),
// Local media: <video>, <audio>, VLC
'mediaStalled': () => controllers.media.mediaStalled(),
'mediaError': (err) => controllers.media.mediaError(err),
'mediaSuccess': () => controllers.media.mediaSuccess(),
'mediaTimeUpdate': () => controllers.media.mediaTimeUpdate(),
'mediaMouseMoved': () => controllers.media.mediaMouseMoved(),
'vlcPlay': () => controllers.media.vlcPlay(),
'vlcNotFound': () => controllers.media.vlcNotFound(),
// Audio Tracks
selectAudioTrack: (index) => controllers.audioTracks().selectAudioTrack(index),
toggleAudioTracksMenu: () => controllers.audioTracks().toggleAudioTracksMenu(),
// Local media: <video>, <audio>, external players
mediaStalled: () => controllers.media().mediaStalled(),
mediaError: (err) => controllers.media().mediaError(err),
mediaSuccess: () => controllers.media().mediaSuccess(),
mediaTimeUpdate: () => controllers.media().mediaTimeUpdate(),
mediaMouseMoved: () => controllers.media().mediaMouseMoved(),
mediaControlsMouseEnter: () => controllers.media().controlsMouseEnter(),
mediaControlsMouseLeave: () => controllers.media().controlsMouseLeave(),
openExternalPlayer: () => controllers.media().openExternalPlayer(),
externalPlayerNotFound: () => controllers.media().externalPlayerNotFound(),
// Remote casting: Chromecast, Airplay, etc
'toggleCastMenu': (deviceType) => lazyLoadCast().toggleMenu(deviceType),
'selectCastDevice': (index) => lazyLoadCast().selectDevice(index),
'stopCasting': () => lazyLoadCast().stop(),
toggleCastMenu: (deviceType) => lazyLoadCast().toggleMenu(deviceType),
selectCastDevice: (index) => lazyLoadCast().selectDevice(index),
stopCasting: () => lazyLoadCast().stop(),
// Preferences screen
'preferences': () => controllers.prefs.show(),
'updatePreferences': (key, value) => controllers.prefs.update(key, value),
'checkDownloadPath': checkDownloadPath,
preferences: () => controllers.prefs().show(),
updatePreferences: (key, value) => controllers.prefs().update(key, value),
checkDownloadPath,
updateGlobalTrackers: (trackers) => setGlobalTrackers(trackers),
startFolderWatcher: () => controllers.folderWatcher().start(),
stopFolderWatcher: () => controllers.folderWatcher().stop(),
// Update (check for new versions on Linux, where there's no auto updater)
'updateAvailable': (version) => controllers.update.updateAvailable(version),
'skipVersion': (version) => controllers.update.skipVersion(version),
updateAvailable: (version) => controllers.update().updateAvailable(version),
skipVersion: (version) => controllers.update().skipVersion(version),
// Navigation between screens (back, forward, ESC, etc)
'exitModal': () => { state.modal = null },
'backToList': backToList,
'escapeBack': escapeBack,
'back': () => state.location.back(),
'forward': () => state.location.forward(),
'cancel': () => state.location.cancel(),
exitModal: () => { state.modal = null },
backToList,
escapeBack,
back: () => state.location.back(),
forward: () => state.location.forward(),
cancel: () => state.location.cancel(),
// Controlling the window
'setDimensions': setDimensions,
'toggleFullScreen': (setTo) => ipcRenderer.send('toggleFullScreen', setTo),
'setTitle': (title) => { state.window.title = title },
'resetTitle': () => { state.window.title = config.APP_WINDOW_TITLE },
setDimensions,
toggleFullScreen: (setTo) => ipcRenderer.send('toggleFullScreen', setTo),
setTitle: (title) => { state.window.title = title },
resetTitle: () => { state.window.title = config.APP_WINDOW_TITLE },
// Everything else
'onOpen': onOpen,
'error': onError,
'uncaughtError': (proc, err) => telemetry.logUncaughtError(proc, err),
'saveState': () => State.save(state),
'saveStateThrottled': () => State.saveThrottled(state),
'update': () => {} // No-op, just trigger an update
onOpen,
error: onError,
uncaughtError: (proc, err) => telemetry.logUncaughtError(proc, err),
stateSave: () => State.save(state),
stateSaveImmediate: () => State.saveImmediate(state),
update: () => {} // No-op, just trigger an update
}
// Events from the UI never modify state directly. Instead they call dispatch()
@@ -251,13 +355,13 @@ function dispatch (action, ...args) {
console.log('dispatch: %s %o', action, args)
}
var handler = dispatchHandlers[action]
const handler = dispatchHandlers[action]
if (handler) handler(...args)
else console.error('Missing dispatch handler: ' + action)
// Update the virtual DOM, unless it's just a mouse move event
if (action !== 'mediaMouseMoved' ||
controllers.playback.showOrHidePlayerControls()) {
controllers.playback().showOrHidePlayerControls()) {
update()
}
}
@@ -270,11 +374,13 @@ function setupIpc () {
ipcRenderer.on('dispatch', (e, ...args) => dispatch(...args))
ipcRenderer.on('fullscreenChanged', onFullscreenChanged)
ipcRenderer.on('windowBoundsChanged', onWindowBoundsChanged)
var tc = controllers.torrent
ipcRenderer.on('wt-infohash', (e, ...args) => tc.torrentInfoHash(...args))
const tc = controllers.torrent()
ipcRenderer.on('wt-parsed', (e, ...args) => tc.torrentParsed(...args))
ipcRenderer.on('wt-metadata', (e, ...args) => tc.torrentMetadata(...args))
ipcRenderer.on('wt-done', (e, ...args) => tc.torrentDone(...args))
ipcRenderer.on('wt-done', () => controllers.torrentList().resumePausedTorrents())
ipcRenderer.on('wt-warning', (e, ...args) => tc.torrentWarning(...args))
ipcRenderer.on('wt-error', (e, ...args) => tc.torrentError(...args))
@@ -289,16 +395,16 @@ function setupIpc () {
ipcRenderer.send('ipcReady')
State.on('savedState', () => ipcRenderer.send('savedState'))
State.on('stateSaved', () => ipcRenderer.send('stateSaved'))
}
// Quits any modal popovers and returns to the torrent list screen
function backToList () {
// Exit any modals and screens with a back button
state.modal = null
state.location.backToFirst(function () {
state.location.backToFirst(() => {
// If we were already on the torrent list, scroll to the top
var contentTag = document.querySelector('.content')
const contentTag = document.querySelector('.content')
if (contentTag) contentTag.scrollTop = 0
})
}
@@ -314,6 +420,10 @@ function escapeBack () {
}
}
function setGlobalTrackers () {
controllers.torrentList().setGlobalTrackers(state.getGlobalTrackers())
}
// Starts all torrents that aren't paused on program startup
function resumeTorrents () {
state.saved.torrents
@@ -324,7 +434,7 @@ function resumeTorrents () {
return torrentSummary
})
.filter((s) => s.status !== 'paused')
.forEach((s) => controllers.torrentList.startTorrentingSummary(s.torrentKey))
.forEach((s) => controllers.torrentList().startTorrentingSummary(s.torrentKey))
}
// Set window dimensions to match video dimensions or fill the screen
@@ -345,48 +455,55 @@ function setDimensions (dimensions) {
state.window.wasMaximized = electron.remote.getCurrentWindow().isMaximized
// Limit window size to screen size
var screenWidth = window.screen.width
var screenHeight = window.screen.height
var aspectRatio = dimensions.width / dimensions.height
var scaleFactor = Math.min(
const screenWidth = window.screen.width
const screenHeight = window.screen.height
const aspectRatio = dimensions.width / dimensions.height
const scaleFactor = Math.min(
Math.min(screenWidth / dimensions.width, 1),
Math.min(screenHeight / dimensions.height, 1)
)
var width = Math.max(
const width = Math.max(
Math.floor(dimensions.width * scaleFactor),
config.WINDOW_MIN_WIDTH
)
var height = Math.max(
const height = Math.max(
Math.floor(dimensions.height * scaleFactor),
config.WINDOW_MIN_HEIGHT
)
ipcRenderer.send('setAspectRatio', aspectRatio)
ipcRenderer.send('setBounds', {x: null, y: null, width, height})
ipcRenderer.send('setBounds', { contentBounds: true, x: null, y: null, width, height })
state.playing.aspectRatio = aspectRatio
}
// Called when the user adds files (.torrent, files to seed, subtitles) to the app
// via any method (drag-drop, drag to app icon, command line)
function onOpen (files) {
if (!Array.isArray(files)) files = [ files ]
if (!Array.isArray(files)) files = [files]
var url = state.location.url()
var allTorrents = files.every(TorrentPlayer.isTorrent)
var allSubtitles = files.every(controllers.subtitles.isSubtitle)
// File API seems to transform "magnet:?foo" in "magnet:///?foo"
// this is a sanitization
files = files.map(file => {
if (typeof file !== 'string') return file
return file.replace(/^magnet:\/+\?/i, 'magnet:?')
})
const url = state.location.url()
const allTorrents = files.every(TorrentPlayer.isTorrent)
const allSubtitles = files.every(controllers.subtitles().isSubtitle)
if (allTorrents) {
// Drop torrents onto the app: go to home screen, add torrents, no matter what
dispatch('backToList')
// All .torrent files? Add them.
files.forEach((file) => controllers.torrentList.addTorrent(file))
files.forEach((file) => controllers.torrentList().addTorrent(file))
} else if (url === 'player' && allSubtitles) {
// Drop subtitles onto a playing video: add subtitles
controllers.subtitles.addSubtitles(files, true)
controllers.subtitles().addSubtitles(files, true)
} else if (url === 'home') {
// Drop files onto home screen: show Create Torrent
state.modal = null
controllers.torrentList.showCreateTorrent(files)
controllers.torrentList().showCreateTorrent(files)
} else {
// Drop files onto any other screen: show error
return onError('Please go back to the torrent list before creating a new torrent.')
@@ -406,15 +523,42 @@ function onError (err) {
update()
}
function onPaste (e) {
if (e.target.tagName.toLowerCase() === 'input') return
const editableHtmlTags = new Set(['input', 'textarea'])
var torrentIds = electron.clipboard.readText().split('\n')
torrentIds.forEach(function (torrentId) {
torrentId = torrentId.trim()
if (torrentId.length === 0) return
controllers.torrentList.addTorrent(torrentId)
})
function onPaste (e) {
if (e && editableHtmlTags.has(e.target.tagName.toLowerCase())) return
controllers.torrentList().addTorrent(electron.clipboard.readText())
update()
}
function onKeydown (e) {
// prevent event fire on user input elements
if (editableHtmlTags.has(e.target.tagName.toLowerCase())) return
const key = e.key
if (key === 'ArrowLeft') {
dispatch('skip', -5)
} else if (key === 'ArrowRight') {
dispatch('skip', 5)
} else if (key === 'ArrowUp') {
dispatch('changeVolume', 0.1)
} else if (key === 'ArrowDown') {
dispatch('changeVolume', -0.1)
} else if (key === 'j') {
dispatch('skip', -10)
} else if (key === 'l') {
dispatch('skip', 10)
} else if (key === 'k') {
dispatch('playPause')
} else if (key === '>') {
dispatch('changePlaybackRate', 1)
} else if (key === '<') {
dispatch('changePlaybackRate', -1)
} else if (key === 'f') {
dispatch('toggleFullScreen')
}
update()
}
@@ -431,7 +575,7 @@ function onBlur () {
}
function onVisibilityChange () {
state.window.isVisible = !document.webkitHidden
state.window.isVisible = !document.hidden
}
function onFullscreenChanged (e, isFullScreen) {
@@ -444,8 +588,15 @@ function onFullscreenChanged (e, isFullScreen) {
update()
}
function onWindowBoundsChanged (e, newBounds) {
if (state.location.url() !== 'player') {
state.saved.bounds = newBounds
dispatch('stateSave')
}
}
function checkDownloadPath () {
fs.stat(state.saved.prefs.downloadPath, function (err, stat) {
fs.stat(state.saved.prefs.downloadPath, (err, stat) => {
if (err) {
state.downloadPathStatus = 'missing'
return console.error(err)

132
src/renderer/pages/app.js Normal file
View File

@@ -0,0 +1,132 @@
const colors = require('material-ui/styles/colors')
const createGetter = require('fn-getter')
const React = require('react')
const darkBaseTheme = require('material-ui/styles/baseThemes/darkBaseTheme').default
const getMuiTheme = require('material-ui/styles/getMuiTheme').default
const MuiThemeProvider = require('material-ui/styles/MuiThemeProvider').default
const Header = require('../components/header')
// Perf optimization: Needed immediately, so do not lazy load it below
const TorrentListPage = require('./torrent-list-page')
const Views = {
home: createGetter(() => TorrentListPage),
player: createGetter(() => require('./player-page')),
'create-torrent': createGetter(() => require('./create-torrent-page')),
preferences: createGetter(() => require('./preferences-page'))
}
const Modals = {
'open-torrent-address-modal': createGetter(
() => require('../components/open-torrent-address-modal')
),
'remove-torrent-modal': createGetter(() => require('../components/remove-torrent-modal')),
'update-available-modal': createGetter(() => require('../components/update-available-modal')),
'unsupported-media-modal': createGetter(() => require('../components/unsupported-media-modal')),
'delete-all-torrents-modal':
createGetter(() => require('../components/delete-all-torrents-modal'))
}
const fontFamily = process.platform === 'win32'
? '"Segoe UI", sans-serif'
: 'BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif'
darkBaseTheme.fontFamily = fontFamily
darkBaseTheme.userAgent = false
darkBaseTheme.palette.primary1Color = colors.grey50
darkBaseTheme.palette.primary2Color = colors.grey50
darkBaseTheme.palette.primary3Color = colors.grey600
darkBaseTheme.palette.accent1Color = colors.redA200
darkBaseTheme.palette.accent2Color = colors.redA400
darkBaseTheme.palette.accent3Color = colors.redA100
let darkMuiTheme
let lightMuiTheme
class App extends React.Component {
render () {
const state = this.props.state
// Hide player controls while playing video, if the mouse stays still for a while
// Never hide the controls when:
// * The mouse is over the controls or we're scrubbing (see CSS)
// * The video is paused
// * The video is playing remotely on Chromecast or Airplay
const hideControls = state.shouldHidePlayerControls()
const cls = [
'view-' + state.location.url(), /* e.g. view-home, view-player */
'is-' + process.platform /* e.g. is-darwin, is-win32, is-linux */
]
if (state.window.isFullScreen) cls.push('is-fullscreen')
if (state.window.isFocused) cls.push('is-focused')
if (hideControls) cls.push('hide-video-controls')
if (!darkMuiTheme) {
darkMuiTheme = getMuiTheme(darkBaseTheme)
}
return (
<MuiThemeProvider muiTheme={darkMuiTheme}>
<div className={'app ' + cls.join(' ')}>
<Header state={state} />
{this.getErrorPopover()}
<div key='content' className='content'>{this.getView()}</div>
{this.getModal()}
</div>
</MuiThemeProvider>
)
}
getErrorPopover () {
const state = this.props.state
const now = new Date().getTime()
const recentErrors = state.errors.filter((x) => now - x.time < 5000)
const hasErrors = recentErrors.length > 0
const errorElems = recentErrors.map((error, i) => <div key={i} className='error'>{error.message}</div>)
return (
<div
key='errors'
className={'error-popover ' + (hasErrors ? 'visible' : 'hidden')}
>
<div key='title' className='title'>Error</div>
{errorElems}
</div>
)
}
getModal () {
const state = this.props.state
if (!state.modal) return
if (!lightMuiTheme) {
const lightBaseTheme = require('material-ui/styles/baseThemes/lightBaseTheme').default
lightBaseTheme.fontFamily = fontFamily
lightBaseTheme.userAgent = false
lightMuiTheme = getMuiTheme(lightBaseTheme)
}
const ModalContents = Modals[state.modal.id]()
return (
<MuiThemeProvider muiTheme={lightMuiTheme}>
<div key='modal' className='modal'>
<div key='modal-background' className='modal-background' />
<div key='modal-content' className='modal-content'>
<ModalContents state={state} />
</div>
</div>
</MuiThemeProvider>
)
}
getView () {
const state = this.props.state
const View = Views[state.location.url()]()
return (<View state={state} />)
}
}
module.exports = App

View File

@@ -0,0 +1,227 @@
const createTorrent = require('create-torrent')
const path = require('path')
const prettyBytes = require('prettier-bytes')
const React = require('react')
const { dispatch, dispatcher } = require('../lib/dispatcher')
const FlatButton = require('material-ui/FlatButton').default
const RaisedButton = require('material-ui/RaisedButton').default
const TextField = require('material-ui/TextField').default
const Checkbox = require('material-ui/Checkbox').default
const CreateTorrentErrorPage = require('../components/create-torrent-error-page')
const Heading = require('../components/heading')
const ShowMore = require('../components/show-more')
// Shows a basic UI to create a torrent, from an already-selected file or folder.
// Includes a "Show Advanced..." button and more advanced UI.
class CreateTorrentPage extends React.Component {
constructor (props) {
super(props)
const state = this.props.state
const info = state.location.current()
// First, extract the base folder that the files are all in
let pathPrefix = info.folderPath
if (!pathPrefix) {
pathPrefix = info.files.map((x) => x.path).reduce(findCommonPrefix)
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
pathPrefix = path.dirname(pathPrefix)
}
}
// Then, exclude .DS_Store and other dotfiles
const files = info.files
.filter((f) => !containsDots(f.path, pathPrefix))
.map((f) => ({ name: f.name, path: f.path, size: f.size }))
if (files.length === 0) return (<CreateTorrentErrorPage state={state} />)
// Then, use the name of the base folder (or sole file, for a single file torrent)
// as the default name. Show all files relative to the base folder.
let defaultName, basePath
if (files.length === 1) {
// Single file torrent: /a/b/foo.jpg -> torrent name 'foo.jpg', path '/a/b'
defaultName = files[0].name
basePath = pathPrefix
} else {
// Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name 'b', path '/a'
defaultName = path.basename(pathPrefix)
basePath = path.dirname(pathPrefix)
}
// Default trackers
const trackers = createTorrent.announceList.join('\n')
this.state = {
comment: '',
isPrivate: false,
pathPrefix,
basePath,
defaultName,
files,
trackers
}
// Create React event handlers only once
this.handleSetIsPrivate = (_, isPrivate) => this.setState({ isPrivate })
this.handleSetComment = (_, comment) => this.setState({ comment })
this.handleSetTrackers = (_, trackers) => this.setState({ trackers })
this.handleSubmit = handleSubmit.bind(this)
}
render () {
const files = this.state.files
// Sanity check: show the number of files and total size
const numFiles = files.length
const totalBytes = files
.map((f) => f.size)
.reduce((a, b) => a + b, 0)
const torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
return (
<div className='create-torrent'>
<Heading level={1}>Create torrent {this.state.defaultName}</Heading>
<div className='torrent-info'>{torrentInfo}</div>
<div className='torrent-attribute'>
<label>Path:</label>
<div>{this.state.pathPrefix}</div>
</div>
<ShowMore
style={{
marginBottom: 10
}}
hideLabel='Hide advanced settings...'
showLabel='Show advanced settings...'
>
{this.renderAdvanced()}
</ShowMore>
<div className='float-right'>
<FlatButton
className='control cancel'
label='Cancel'
style={{
marginRight: 10
}}
onClick={dispatcher('cancel')}
/>
<RaisedButton
className='control create-torrent-button'
label='Create Torrent'
primary
onClick={this.handleSubmit}
/>
</div>
</div>
)
}
// Renders everything after clicking Show Advanced...:
// * Is Private? (private torrents, not announced to DHT)
// * Announce list (trackers)
// * Comment
renderAdvanced () {
// Create file list
const maxFileElems = 100
const files = this.state.files
const fileElems = files.slice(0, maxFileElems).map((file, i) => {
const relativePath = path.relative(this.state.pathPrefix, file.path)
return (<div key={i}>{relativePath}</div>)
})
if (files.length > maxFileElems) {
fileElems.push(<div key='more'>+ {files.length - maxFileElems} more</div>)
}
// Align the text fields
const textFieldStyle = { width: '' }
const textareaStyle = { margin: 0 }
return (
<div key='advanced' className='create-torrent-advanced'>
<div key='private' className='torrent-attribute'>
<label>Private:</label>
<Checkbox
className='torrent-is-private control'
style={{ display: '' }}
checked={this.state.isPrivate}
onCheck={this.handleSetIsPrivate}
/>
</div>
<div key='trackers' className='torrent-attribute'>
<label>Trackers:</label>
<TextField
className='torrent-trackers control'
style={textFieldStyle}
textareaStyle={textareaStyle}
multiLine
rows={2}
rowsMax={10}
value={this.state.trackers}
onChange={this.handleSetTrackers}
/>
</div>
<div key='comment' className='torrent-attribute'>
<label>Comment:</label>
<TextField
className='torrent-comment control'
style={textFieldStyle}
textareaStyle={textareaStyle}
hintText='Optionally describe your torrent...'
multiLine
rows={2}
rowsMax={10}
value={this.state.comment}
onChange={this.handleSetComment}
/>
</div>
<div key='files' className='torrent-attribute'>
<label>Files:</label>
<div>{fileElems}</div>
</div>
</div>
)
}
}
function handleSubmit () {
const announceList = this.state.trackers
.split('\n')
.map((s) => s.trim())
.filter((s) => s !== '')
const options = {
// We can't let the user choose their own name if we want WebTorrent
// to use the files in place rather than creating a new folder.
name: this.state.defaultName,
path: this.state.basePath,
files: this.state.files,
announce: announceList,
comment: this.state.comment.trim()
}
// If torrent is not private, leave private flag unset. This ensures that
// the torrent info hash will match the result generated by other tools,
// including webtorrent-cli.
if (this.state.isPrivate) options.private = true
dispatch('createTorrent', options)
}
// Finds the longest common prefix
function findCommonPrefix (a, b) {
let i
for (i = 0; i < a.length && i < b.length; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
}
if (i === a.length) return a
if (i === b.length) return b
return a.substring(0, i)
}
function containsDots (path, pathPrefix) {
const suffix = path.substring(pathPrefix.length).replace(/\\/g, '/')
return ('/' + suffix).includes('/.')
}
module.exports = CreateTorrentPage

View File

@@ -0,0 +1,993 @@
/* globals MediaMetadata */
const React = require('react')
const BitField = require('bitfield').default
const prettyBytes = require('prettier-bytes')
const TorrentSummary = require('../lib/torrent-summary')
const Playlist = require('../lib/playlist')
const { dispatch, dispatcher } = require('../lib/dispatcher')
const config = require('../../config')
const { calculateEta } = require('../lib/time')
// Shows a streaming video player. Standard features + Chromecast + Airplay
module.exports = class Player extends React.Component {
render () {
// Show the video as large as will fit in the window, play immediately
// If the video is on Chromecast or Airplay, show a title screen instead
const state = this.props.state
const showVideo = state.playing.location === 'local'
const showControls = state.playing.location !== 'external'
return (
<div
className='player'
onWheel={handleVolumeWheel}
onMouseMove={dispatcher('mediaMouseMoved')}
>
{showVideo ? renderMedia(state) : renderCastScreen(state)}
{showControls ? renderPlayerControls(state) : null}
</div>
)
}
onComponentWillUnmount () {
// Unload the media element so that Chromium stops trying to fetch data
const tag = document.querySelector('audio,video')
if (!tag) return
tag.pause()
tag.src = ''
tag.load()
navigator.mediaSession.metadata = null
}
}
// Handles volume change by wheel
function handleVolumeWheel (e) {
dispatch('changeVolume', (-e.deltaY | e.deltaX) / 500)
}
function renderMedia (state) {
if (!state.server) return
// Unfortunately, play/pause can't be done just by modifying HTML.
// Instead, grab the DOM node and play/pause it if necessary
// Get the <video> or <audio> tag
const mediaElement = document.querySelector(state.playing.type)
if (mediaElement !== null) {
if (navigator.mediaSession.metadata === null && mediaElement.played.length !== 0) {
navigator.mediaSession.metadata = new MediaMetadata({
title: state.playing.fileName
})
navigator.mediaSession.setActionHandler('pause', () => {
dispatch('playPause')
})
navigator.mediaSession.setActionHandler('play', () => {
dispatch('playPause')
})
if (Playlist.hasNext(state)) {
navigator.mediaSession.setActionHandler('nexttrack', () => {
dispatch('nextTrack')
})
}
if (Playlist.hasPrevious(state)) {
navigator.mediaSession.setActionHandler('previoustrack', () => {
dispatch('previousTrack')
})
}
}
if (state.playing.isPaused && !mediaElement.paused) {
mediaElement.pause()
} else if (!state.playing.isPaused && mediaElement.paused) {
mediaElement.play()
}
// When the user clicks or drags on the progress bar, jump to that position
if (state.playing.jumpToTime != null) {
mediaElement.currentTime = state.playing.jumpToTime
state.playing.jumpToTime = null
}
if (state.playing.playbackRate !== mediaElement.playbackRate) {
mediaElement.playbackRate = state.playing.playbackRate
}
// Recover previous volume
if (state.previousVolume !== null && isFinite(state.previousVolume)) {
mediaElement.volume = state.previousVolume
state.previousVolume = null
}
// Set volume
if (state.playing.setVolume !== null && isFinite(state.playing.setVolume)) {
mediaElement.volume = state.playing.setVolume
state.playing.setVolume = null
}
// Switch to the newly added subtitle track, if available
const tracks = mediaElement.textTracks || []
for (let j = 0; j < tracks.length; j++) {
const isSelectedTrack = j === state.playing.subtitles.selectedIndex
tracks[j].mode = isSelectedTrack ? 'showing' : 'hidden'
}
// Save video position
const file = state.getPlayingFileSummary()
file.currentTime = state.playing.currentTime = mediaElement.currentTime
file.duration = state.playing.duration = mediaElement.duration
// Save selected subtitle
if (state.playing.subtitles.selectedIndex !== -1) {
const index = state.playing.subtitles.selectedIndex
file.selectedSubtitle = state.playing.subtitles.tracks[index].filePath
} else if (file.selectedSubtitle != null) {
delete file.selectedSubtitle
}
// Switch to selected audio track
const audioTracks = mediaElement.audioTracks || []
for (let j = 0; j < audioTracks.length; j++) {
const isSelectedTrack = j === state.playing.audioTracks.selectedIndex
audioTracks[j].enabled = isSelectedTrack
}
state.playing.volume = mediaElement.volume
}
// Add subtitles to the <video> tag
const trackTags = []
if (state.playing.subtitles.selectedIndex >= 0) {
state.playing.subtitles.tracks.forEach((track, i) => {
const isSelected = state.playing.subtitles.selectedIndex === i
trackTags.push(
<track
key={i}
default={isSelected}
label={track.label}
kind='subtitles'
src={track.buffer}
/>
)
})
}
// Create the <audio> or <video> tag
const MediaTagName = state.playing.type
const mediaTag = (
<MediaTagName
src={Playlist.getCurrentLocalURL(state)}
onDoubleClick={dispatcher('toggleFullScreen')}
onClick={dispatcher('playPause')}
onLoadedMetadata={onLoadedMetadata}
onEnded={onEnded}
onStalled={dispatcher('mediaStalled')}
onError={dispatcher('mediaError')}
onTimeUpdate={dispatcher('mediaTimeUpdate')}
onEncrypted={dispatcher('mediaEncrypted')}
>
{trackTags}
</MediaTagName>
)
// Show the media.
return (
<div
key='letterbox'
className='letterbox'
onMouseMove={dispatcher('mediaMouseMoved')}
>
{mediaTag}
{renderOverlay(state)}
</div>
)
function onLoadedMetadata (e) {
const mediaElement = e.target
// check if we can decode video and audio track
if (state.playing.type === 'video') {
if (mediaElement.videoTracks.length === 0) {
dispatch('mediaError', 'Video codec unsupported')
}
if (mediaElement.audioTracks.length === 0) {
dispatch('mediaError', 'Audio codec unsupported')
}
dispatch('mediaSuccess')
const dimensions = {
width: mediaElement.videoWidth,
height: mediaElement.videoHeight
}
// As soon as we know the video dimensions, resize the window
dispatch('setDimensions', dimensions)
// set audioTracks
const tracks = []
for (let i = 0; i < mediaElement.audioTracks.length; i++) {
tracks.push({
label: mediaElement.audioTracks[i].label || `Track ${i + 1}`,
language: mediaElement.audioTracks[i].language
})
}
state.playing.audioTracks.tracks = tracks
state.playing.audioTracks.selectedIndex = 0
}
// check if we can decode audio track
if (state.playing.type === 'audio') {
if (mediaElement.audioTracks.length === 0) {
dispatch('mediaError', 'Audio codec unsupported')
}
dispatch('mediaSuccess')
}
}
function onEnded () {
if (Playlist.hasNext(state)) {
dispatch('nextTrack')
} else {
// When the last video completes, pause the video instead of looping
state.playing.isPaused = true
if (state.window.isFullScreen) dispatch('toggleFullScreen')
}
}
}
function renderOverlay (state) {
const elems = []
const audioMetadataElem = renderAudioMetadata(state)
const spinnerElem = renderLoadingSpinner(state)
if (audioMetadataElem) elems.push(audioMetadataElem)
if (spinnerElem) elems.push(spinnerElem)
// Video fills the window, centered with black bars if necessary
// Audio gets a static poster image and a summary of the file metadata.
let style
if (state.playing.type === 'audio') {
style = { backgroundImage: cssBackgroundImagePoster(state) }
} else if (elems.length !== 0) {
style = { backgroundImage: cssBackgroundImageDarkGradient() }
} else {
// Video playing, so no spinner. No overlay needed
return
}
return (
<div key='overlay' className='media-overlay-background' style={style}>
<div className='media-overlay'>{elems}</div>
</div>
)
}
/**
* Render track or disk number string
* @param common metadata.common part
* @param key should be either 'track' or 'disk'
* @return track or disk number metadata as JSX block
*/
function renderTrack (common, key) {
// Audio metadata: track-number
if (common[key] && common[key].no) {
let str = `${common[key].no}`
if (common[key].of) {
str += ` of ${common[key].of}`
}
const style = { textTransform: 'capitalize' }
return (
<div className={`audio-${key}`}>
<label style={style}>{key}</label> {str}
</div>
)
}
}
function renderAudioMetadata (state) {
const fileSummary = state.getPlayingFileSummary()
if (!fileSummary.audioInfo) return
const common = fileSummary.audioInfo.common || {}
// Get audio track info
const title = common.title ? common.title : fileSummary.name
// Show a small info box in the middle of the screen with title/album/etc
const elems = []
// Audio metadata: artist(s)
const artist = common.artist || common.albumartist
if (artist) {
elems.push((
<div key='artist' className='audio-artist'>
<label>Artist</label>{artist}
</div>
))
}
// Audio metadata: disk & track-number
const count = ['track', 'disk']
count.forEach(key => {
const nrElem = renderTrack(common, key)
if (nrElem) {
elems.push(nrElem)
}
})
// Audio metadata: album
if (common.album) {
elems.push((
<div key='album' className='audio-album'>
<label>Album</label>{common.album}
</div>
))
}
// Audio metadata: year
if (common.year) {
elems.push((
<div key='year' className='audio-year'>
<label>Year</label>{common.year}
</div>
))
}
// Audio metadata: release information (label & catalog-number)
if (common.label || common.catalognumber) {
const releaseInfo = []
if (common.label && common.catalognumber &&
common.label.length === common.catalognumber.length) {
// Assume labels & catalog-numbers are pairs
for (let n = 0; n < common.label.length; ++n) {
releaseInfo.push(common.label[0] + ' / ' + common.catalognumber[n])
}
} else {
if (common.label) {
releaseInfo.push(...common.label)
}
if (common.catalognumber) {
releaseInfo.push(...common.catalognumber)
}
}
elems.push((
<div key='release' className='audio-release'>
<label>Release</label>{releaseInfo.join(', ')}
</div>
))
}
// Audio metadata: format
const format = []
fileSummary.audioInfo.format = fileSummary.audioInfo.format || ''
if (fileSummary.audioInfo.format.container) {
format.push(fileSummary.audioInfo.format.container)
}
if (fileSummary.audioInfo.format.codec &&
fileSummary.audioInfo.format.container !== fileSummary.audioInfo.format.codec) {
format.push(fileSummary.audioInfo.format.codec)
}
if (fileSummary.audioInfo.format.bitrate) {
format.push(Math.round(fileSummary.audioInfo.format.bitrate / 1000) + ' kbit/s') // 128 kbit/s
}
if (fileSummary.audioInfo.format.sampleRate) {
format.push(Math.round(fileSummary.audioInfo.format.sampleRate / 100) / 10 + ' kHz')
}
if (fileSummary.audioInfo.format.bitsPerSample) {
format.push(fileSummary.audioInfo.format.bitsPerSample + '-bit')
}
if (format.length > 0) {
elems.push((
<div key='format' className='audio-format'>
<label>Format</label>{format.join(', ')}
</div>
))
}
// Audio metadata: comments
if (common.comment) {
elems.push((
<div key='comments' className='audio-comments'>
<label>Comments</label>{common.comment.join(' / ')}
</div>
))
}
// Align the title with the other info, if available. Otherwise, center title
const emptyLabel = (<label />)
elems.unshift((
<div key='title' className='audio-title'>
{elems.length ? emptyLabel : undefined}{title}
</div>
))
return (<div key='audio-metadata' className='audio-metadata'>{elems}</div>)
}
function renderLoadingSpinner (state) {
if (state.playing.isPaused) return
const isProbablyStalled = state.playing.isStalled ||
(new Date().getTime() - state.playing.lastTimeUpdate > 2000)
if (!isProbablyStalled) return
const prog = state.getPlayingTorrentSummary().progress || {}
let fileProgress = 0
if (prog.files) {
const file = prog.files[state.playing.fileIndex]
fileProgress = Math.floor(100 * file.numPiecesPresent / file.numPieces)
}
return (
<div key='loading' className='media-stalled'>
<div key='loading-spinner' className='loading-spinner' />
<div key='loading-progress' className='loading-status ellipsis'>
<span><span className='progress'>{fileProgress}%</span> downloaded</span>
<span> ↓ {prettyBytes(prog.downloadSpeed || 0)}/s</span>
<span> ↑ {prettyBytes(prog.uploadSpeed || 0)}/s</span>
</div>
</div>
)
}
function renderCastScreen (state) {
let castIcon, castType, isCast
if (state.playing.location.startsWith('chromecast')) {
castIcon = 'cast_connected'
castType = 'Chromecast'
isCast = true
} else if (state.playing.location.startsWith('airplay')) {
castIcon = 'airplay'
castType = 'AirPlay'
isCast = true
} else if (state.playing.location.startsWith('dlna')) {
castIcon = 'tv'
castType = 'DLNA'
isCast = true
} else if (state.playing.location === 'external') {
castIcon = 'tv'
castType = state.getExternalPlayerName()
isCast = false
} else if (state.playing.location === 'error') {
castIcon = 'error_outline'
castType = 'Unable to Play'
isCast = false
}
const isStarting = state.playing.location.endsWith('-pending')
const castName = state.playing.castName
const fileName = state.getPlayingFileSummary().name || ''
let castStatus
if (isCast && isStarting) castStatus = 'Connecting to ' + castName + '...'
else if (isCast && !isStarting) castStatus = 'Connected to ' + castName
else castStatus = ''
const prog = state.getPlayingTorrentSummary().progress || {}
// Show a nice title image, if possible
const style = {
backgroundImage: cssBackgroundImagePoster(state)
}
function renderEta (total, downloaded) {
const missing = (total || 0) - (downloaded || 0)
const downloadSpeed = prog.downloadSpeed || 0
if (downloadSpeed === 0 || missing === 0) return
const etaStr = calculateEta(missing, downloadSpeed)
return (<span>{etaStr}</span>)
}
function renderDownloadProgress () {
if (!prog.files) return
const fileProg = prog.files[state.playing.fileIndex]
const fileProgress = fileProg.numPiecesPresent / fileProg.numPieces
const fileLength = state.getPlayingFileSummary().length
const fileDownloaded = fileProgress * fileLength
const progress = Math.round(100 * fileProgress)
const total = prettyBytes(fileLength)
const completed = prettyBytes(fileDownloaded)
const downloadSpeed = prettyBytes(prog.downloadSpeed || 0)
const uploadSpeed = prettyBytes(prog.uploadSpeed || 0)
let sizes
if (fileProgress < 1) {
sizes = <span> | {completed} / {total}</span>
} else {
sizes = <span> | {completed}</span>
}
return (
<div key='download-progress'>
<span className='progress'>{progress}% downloaded {sizes}</span>
<br />
<span>↓ {downloadSpeed}/s ↑ {uploadSpeed}/s | {prog.numPeers || 0} peer(s)</span>
<br />
{renderEta(fileLength, fileDownloaded)}
</div>
)
}
return (
<div key='cast' className='letterbox' style={style}>
<div className='cast-screen'>
<i className='icon'>{castIcon}</i>
<div key='type' className='cast-type'>{castType}</div>
<div key='status' className='cast-status'>{castStatus}</div>
<div key='name' className='name'>{fileName}</div>
{renderDownloadProgress()}
</div>
</div>
)
}
function renderCastOptions (state) {
if (!state.devices.castMenu) return
const { location, devices } = state.devices.castMenu
const player = state.devices[location]
const items = devices.map((device, ix) => {
const isSelected = player.device === device
const name = device.name
return (
<li key={ix} onClick={dispatcher('selectCastDevice', ix)}>
<i className='icon'>{isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
{' '}
{name}
</li>
)
})
return (
<ul key='cast-options' className='options-list'>
{items}
</ul>
)
}
function renderSubtitleOptions (state) {
const subtitles = state.playing.subtitles
if (!subtitles.tracks.length || !subtitles.showMenu) return
const items = subtitles.tracks.map((track, ix) => {
const isSelected = state.playing.subtitles.selectedIndex === ix
return (
<li key={ix} onClick={dispatcher('selectSubtitle', ix)}>
<i className='icon'>{'radio_button_' + (isSelected ? 'checked' : 'unchecked')}</i>
{track.label}
</li>
)
})
const noneSelected = state.playing.subtitles.selectedIndex === -1
const noneClass = 'radio_button_' + (noneSelected ? 'checked' : 'unchecked')
return (
<ul key='subtitle-options' className='options-list'>
{items}
<li onClick={dispatcher('selectSubtitle', -1)}>
<i className='icon'>{noneClass}</i>
None
</li>
</ul>
)
}
function renderAudioTrackOptions (state) {
const audioTracks = state.playing.audioTracks
if (!audioTracks.tracks.length || !audioTracks.showMenu) return
const items = audioTracks.tracks.map((track, ix) => {
const isSelected = state.playing.audioTracks.selectedIndex === ix
return (
<li key={ix} onClick={dispatcher('selectAudioTrack', ix)}>
<i className='icon'>{'radio_button_' + (isSelected ? 'checked' : 'unchecked')}</i>
{track.label}
</li>
)
})
return (
<ul key='audio-track-options' className='options-list'>
{items}
</ul>
)
}
function renderPlayerControls (state) {
const positionPercent = 100 * state.playing.currentTime / state.playing.duration
const playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 3px)' }
const captionsClass = state.playing.subtitles.tracks.length === 0
? 'disabled'
: state.playing.subtitles.selectedIndex >= 0
? 'active'
: ''
const multiAudioClass = state.playing.audioTracks.tracks.length > 1
? 'active'
: 'disabled'
const prevClass = Playlist.hasPrevious(state) ? '' : 'disabled'
const nextClass = Playlist.hasNext(state) ? '' : 'disabled'
const elements = [
renderPreview(state),
<div key='playback-bar' className='playback-bar'>
{renderLoadingBar(state)}
<div
key='cursor'
className='playback-cursor'
style={playbackCursorStyle}
/>
<div
key='scrub-bar'
className='scrub-bar'
draggable='true'
onMouseMove={handleScrubPreview}
onMouseOut={clearPreview}
onDragStart={handleDragStart}
onClick={handleScrub}
onDrag={handleScrub}
/>
</div>,
<i
key='skip-previous'
className={'icon skip-previous float-left ' + prevClass}
onClick={dispatcher('previousTrack')}
role='button'
aria-label='Previous track'
>
skip_previous
</i>,
<i
key='play'
className='icon play-pause float-left'
onClick={dispatcher('playPause')}
role='button'
aria-label={state.playing.isPaused ? 'Play' : 'Pause'}
>
{state.playing.isPaused ? 'play_arrow' : 'pause'}
</i>,
<i
key='skip-next'
className={'icon skip-next float-left ' + nextClass}
onClick={dispatcher('nextTrack')}
role='button'
aria-label='Next track'
>
skip_next
</i>,
<i
key='fullscreen'
className='icon fullscreen float-right'
onClick={dispatcher('toggleFullScreen')}
role='button'
aria-label={state.window.isFullScreen ? 'Exit full screen' : 'Enter full screen'}
>
{state.window.isFullScreen ? 'fullscreen_exit' : 'fullscreen'}
</i>
]
if (state.playing.type === 'video') {
// Show closed captions icon
elements.push((
<i
key='subtitles'
className={'icon closed-caption float-right ' + captionsClass}
onClick={handleSubtitles}
role='button'
aria-label='Closed captions'
>
closed_caption
</i>
), (
<i
key='audio-tracks'
className={'icon multi-audio float-right ' + multiAudioClass}
onClick={handleAudioTracks}
>
library_music
</i>
))
}
// If we've detected a Chromecast or AppleTV, the user can play video there
const castTypes = ['chromecast', 'airplay', 'dlna']
const isCastingAnywhere = castTypes.some(
(castType) => state.playing.location.startsWith(castType))
// Add the cast buttons. Icons for each cast type, connected/disconnected:
const buttonIcons = {
chromecast: { true: 'cast_connected', false: 'cast' },
airplay: { true: 'airplay', false: 'airplay' },
dlna: { true: 'tv', false: 'tv' }
}
castTypes.forEach(castType => {
// Do we show this button (eg. the Chromecast button) at all?
const isCasting = state.playing.location.startsWith(castType)
const player = state.devices[castType]
if ((!player || player.getDevices().length === 0) && !isCasting) return
// Show the button. Three options for eg the Chromecast button:
let buttonClass, buttonHandler
if (isCasting) {
// Option 1: we are currently connected to Chromecast. Button stops the cast.
buttonClass = 'active'
buttonHandler = dispatcher('stopCasting')
} else if (isCastingAnywhere) {
// Option 2: we are currently connected somewhere else. Button disabled.
buttonClass = 'disabled'
buttonHandler = undefined
} else {
// Option 3: we are not connected anywhere. Button opens Chromecast menu.
buttonClass = ''
buttonHandler = dispatcher('toggleCastMenu', castType)
}
const buttonIcon = buttonIcons[castType][isCasting]
elements.push((
<i
key={castType}
className={'icon device float-right ' + buttonClass}
onClick={buttonHandler}
>
{buttonIcon}
</i>
))
})
// Render volume slider
const volume = state.playing.volume
const volumeIcon = 'volume_' + (
volume === 0
? 'off'
: volume < 0.3
? 'mute'
: volume < 0.6
? 'down'
: 'up'
)
const volumeStyle = {
background: '-webkit-gradient(linear, left top, right top, ' +
'color-stop(' + (volume * 100) + '%, #eee), ' +
'color-stop(' + (volume * 100) + '%, #727272))'
}
elements.push((
<div key='volume' className='volume float-left'>
<i
className='icon volume-icon float-left'
onMouseDown={handleVolumeMute}
role='button'
aria-label='Mute'
>
{volumeIcon}
</i>
<input
className='volume-slider float-right'
type='range' min='0' max='1' step='0.05'
value={volume}
onChange={handleVolumeScrub}
style={volumeStyle}
/>
</div>
))
// Show video playback progress
const currentTimeStr = formatTime(state.playing.currentTime, state.playing.duration)
const durationStr = formatTime(state.playing.duration, state.playing.duration)
elements.push((
<span key='time' className='time float-left'>
{currentTimeStr} / {durationStr}
</span>
))
// Render playback rate
if (state.playing.playbackRate !== 1) {
elements.push((
<span key='rate' className='rate float-left'>
{state.playing.playbackRate}x
</span>
))
}
const emptyImage = new window.Image(0, 0)
emptyImage.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'
function handleDragStart (e) {
if (e.dataTransfer) {
const dt = e.dataTransfer
// Prevent the cursor from changing, eg to a green + icon on Mac
dt.effectAllowed = 'none'
// Prevent ghost image
dt.setDragImage(emptyImage, 0, 0)
}
}
// Handles a scrub hover (preview another position in the video)
function handleScrubPreview (e) {
// Only show for videos
if (!e.clientX || state.playing.type !== 'video') return
dispatch('mediaMouseMoved')
dispatch('preview', e.clientX)
}
function clearPreview () {
if (state.playing.type !== 'video') return
dispatch('clearPreview')
}
// Handles a click or drag to scrub (jump to another position in the video)
function handleScrub (e) {
if (!e.clientX) return
dispatch('mediaMouseMoved')
const windowWidth = document.querySelector('body').clientWidth
const fraction = e.clientX / windowWidth
const position = fraction * state.playing.duration /* seconds */
dispatch('skipTo', position)
}
// Handles volume muting and Unmuting
function handleVolumeMute () {
if (state.playing.volume === 0.0) {
dispatch('setVolume', 1.0)
} else {
dispatch('setVolume', 0.0)
}
}
// Handles volume slider scrub
function handleVolumeScrub (e) {
dispatch('setVolume', e.target.value)
}
function handleSubtitles (e) {
if (!state.playing.subtitles.tracks.length || e.ctrlKey || e.metaKey) {
// if no subtitles available select it
dispatch('openSubtitles')
} else {
dispatch('toggleSubtitlesMenu')
}
}
function handleAudioTracks () {
dispatch('toggleAudioTracksMenu')
}
return (
<div
key='controls' className='controls'
onMouseEnter={dispatcher('mediaControlsMouseEnter')}
onMouseLeave={dispatcher('mediaControlsMouseLeave')}
>
{elements}
{renderCastOptions(state)}
{renderSubtitleOptions(state)}
{renderAudioTrackOptions(state)}
</div>
)
}
function renderPreview (state) {
const { previewXCoord = null } = state.playing
// Calculate time from x-coord as fraction of track width
const windowWidth = document.querySelector('body').clientWidth
const fraction = previewXCoord / windowWidth
const time = fraction * state.playing.duration /* seconds */
const height = 70
let width = 0
const previewEl = document.querySelector('video#preview')
if (previewEl !== null && previewXCoord !== null) {
previewEl.currentTime = time
// Auto adjust width to maintain video aspect ratio
width = Math.floor((previewEl.videoWidth / previewEl.videoHeight) * height)
}
// Center preview window on mouse cursor,
// while avoiding falling off the left or right edges
const xPos = Math.min(Math.max(previewXCoord - (width / 2), 5), windowWidth - width - 5)
return (
<div
key='preview' style={{
position: 'absolute',
bottom: 50,
left: xPos,
display: previewXCoord == null && 'none' // Hide preview when XCoord unset
}}
>
<div style={{ width, height, backgroundColor: 'black' }}>
<video
src={Playlist.getCurrentLocalURL(state)}
id='preview'
style={{ border: '1px solid lightgrey', borderRadius: 2 }}
/>
</div>
<p
style={{
textAlign: 'center', margin: 5, textShadow: '0 0 2px rgba(0,0,0,.5)', color: '#eee'
}}
>
{formatTime(time, state.playing.duration)}
</p>
</div>
)
}
// Renders the loading bar. Shows which parts of the torrent are loaded, which
// can be 'spongey' / non-contiguous
function renderLoadingBar (state) {
if (config.IS_TEST) return // Don't integration test the loading bar. Screenshots won't match.
const torrentSummary = state.getPlayingTorrentSummary()
if (!torrentSummary.progress) {
return null
}
// Find all contiguous parts of the torrent which are loaded
const prog = torrentSummary.progress
const fileProg = prog.files[state.playing.fileIndex]
if (!fileProg) return null
const parts = []
let lastPiecePresent = false
for (let i = fileProg.startPiece; i <= fileProg.endPiece; i++) {
const partPresent = BitField.prototype.get.call(prog.bitfield, i)
if (partPresent && !lastPiecePresent) {
parts.push({ start: i - fileProg.startPiece, count: 1 })
} else if (partPresent) {
parts[parts.length - 1].count++
}
lastPiecePresent = partPresent
}
// Output some bars to show which parts of the file are loaded
const loadingBarElems = parts.map((part, i) => {
const style = {
left: (100 * part.start / fileProg.numPieces) + '%',
width: (100 * part.count / fileProg.numPieces) + '%'
}
return (<div key={i} className='loading-bar-part' style={style} />)
})
return (<div key='loading-bar' className='loading-bar'>{loadingBarElems}</div>)
}
// Returns the CSS background-image string for a poster image + dark vignette
function cssBackgroundImagePoster (state) {
const torrentSummary = state.getPlayingTorrentSummary()
const posterPath = TorrentSummary.getPosterPath(torrentSummary)
if (!posterPath) return ''
return cssBackgroundImageDarkGradient() + `, url('${posterPath}')`
}
function cssBackgroundImageDarkGradient () {
return 'radial-gradient(circle at center, ' +
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)'
}
function formatTime (time, total) {
if (typeof time !== 'number' || Number.isNaN(time)) {
return '0:00'
}
const totalHours = Math.floor(total / 3600)
const totalMinutes = Math.floor(total / 60)
const hours = Math.floor(time / 3600)
let minutes = Math.floor(time % 3600 / 60)
if (totalMinutes > 9 && minutes < 10) {
minutes = '0' + minutes
}
const seconds = `0${Math.floor(time % 60)}`.slice(-2)
return (totalHours > 0 ? hours + ':' : '') + minutes + ':' + seconds
}

View File

@@ -0,0 +1,336 @@
const React = require('react')
const PropTypes = require('prop-types')
const colors = require('material-ui/styles/colors')
const Checkbox = require('material-ui/Checkbox').default
const RaisedButton = require('material-ui/RaisedButton').default
const TextField = require('material-ui/TextField').default
const Heading = require('../components/heading')
const PathSelector = require('../components/path-selector')
const { dispatch } = require('../lib/dispatcher')
const config = require('../../config')
class PreferencesPage extends React.Component {
constructor (props) {
super(props)
this.handleDownloadPathChange =
this.handleDownloadPathChange.bind(this)
this.handleOpenExternalPlayerChange =
this.handleOpenExternalPlayerChange.bind(this)
this.handleExternalPlayerPathChange =
this.handleExternalPlayerPathChange.bind(this)
this.handleStartupChange =
this.handleStartupChange.bind(this)
this.handleSoundNotificationsChange =
this.handleSoundNotificationsChange.bind(this)
this.handleSetGlobalTrackers =
this.handleSetGlobalTrackers.bind(this)
const globalTrackers = this.props.state.getGlobalTrackers().join('\n')
this.state = {
globalTrackers
}
}
downloadPathSelector () {
return (
<Preference>
<PathSelector
dialog={{
title: 'Select download directory',
properties: ['openDirectory']
}}
onChange={this.handleDownloadPathChange}
title='Download location'
value={this.props.state.saved.prefs.downloadPath}
/>
</Preference>
)
}
handleDownloadPathChange (filePath) {
dispatch('updatePreferences', 'downloadPath', filePath)
}
openExternalPlayerCheckbox () {
return (
<Preference>
<Checkbox
className='control'
checked={!this.props.state.saved.prefs.openExternalPlayer}
label='Play torrent media files using WebTorrent'
onCheck={this.handleOpenExternalPlayerChange}
/>
</Preference>
)
}
handleOpenExternalPlayerChange (e, isChecked) {
dispatch('updatePreferences', 'openExternalPlayer', !isChecked)
}
highestPlaybackPriorityCheckbox () {
return (
<Preference>
<Checkbox
className='control'
checked={this.props.state.saved.prefs.highestPlaybackPriority}
label='Highest Playback Priority'
onCheck={this.handleHighestPlaybackPriorityChange}
/>
<p>Pauses all active torrents to allow playback to use all of the available bandwidth.</p>
</Preference>
)
}
handleHighestPlaybackPriorityChange (e, isChecked) {
dispatch('updatePreferences', 'highestPlaybackPriority', isChecked)
}
externalPlayerPathSelector () {
const playerPath = this.props.state.saved.prefs.externalPlayerPath
const playerName = this.props.state.getExternalPlayerName()
const description = this.props.state.saved.prefs.openExternalPlayer
? `Torrent media files will always play in ${playerName}.`
: `Torrent media files will play in ${playerName} if WebTorrent cannot play them.`
return (
<Preference>
<p>{description}</p>
<PathSelector
dialog={{
title: 'Select media player app',
properties: ['openFile']
}}
onChange={this.handleExternalPlayerPathChange}
title='External player'
value={playerPath}
/>
</Preference>
)
}
handleExternalPlayerPathChange (filePath) {
dispatch('updatePreferences', 'externalPlayerPath', filePath)
}
autoAddTorrentsCheckbox () {
return (
<Preference>
<Checkbox
className='control'
checked={this.props.state.saved.prefs.autoAddTorrents}
label='Watch for new .torrent files and add them immediately'
onCheck={(e, value) => { this.handleAutoAddTorrentsChange(e, value) }}
/>
</Preference>
)
}
handleAutoAddTorrentsChange (e, isChecked) {
const torrentsFolderPath = this.props.state.saved.prefs.torrentsFolderPath
if (isChecked && !torrentsFolderPath) {
alert('Select a torrents folder first.') // eslint-disable-line
e.preventDefault()
return
}
dispatch('updatePreferences', 'autoAddTorrents', isChecked)
if (isChecked) {
dispatch('startFolderWatcher')
return
}
dispatch('stopFolderWatcher')
}
torrentsFolderPathSelector () {
const torrentsFolderPath = this.props.state.saved.prefs.torrentsFolderPath
return (
<Preference>
<PathSelector
dialog={{
title: 'Select folder to watch for new torrents',
properties: ['openDirectory']
}}
onChange={this.handleTorrentsFolderPathChange}
title='Folder to watch'
value={torrentsFolderPath}
/>
</Preference>
)
}
handleTorrentsFolderPathChange (filePath) {
dispatch('updatePreferences', 'torrentsFolderPath', filePath)
}
setDefaultAppButton () {
const isFileHandler = this.props.state.saved.prefs.isFileHandler
if (isFileHandler) {
return (
<Preference>
<p>WebTorrent is your default torrent app. Hooray!</p>
</Preference>
)
}
return (
<Preference>
<p>WebTorrent is not currently the default torrent app.</p>
<RaisedButton
className='control'
onClick={this.handleSetDefaultApp}
label='Make WebTorrent the default'
/>
</Preference>
)
}
handleStartupChange (e, isChecked) {
dispatch('updatePreferences', 'startup', isChecked)
}
setStartupCheckbox () {
if (config.IS_PORTABLE) {
return
}
return (
<Preference>
<Checkbox
className='control'
checked={this.props.state.saved.prefs.startup}
label='Open WebTorrent on startup'
onCheck={this.handleStartupChange}
/>
</Preference>
)
}
soundNotificationsCheckbox () {
return (
<Preference>
<Checkbox
className='control'
checked={this.props.state.saved.prefs.soundNotifications}
label='Enable sounds'
onCheck={this.handleSoundNotificationsChange}
/>
</Preference>
)
}
handleSoundNotificationsChange (e, isChecked) {
dispatch('updatePreferences', 'soundNotifications', isChecked)
}
handleSetDefaultApp () {
dispatch('updatePreferences', 'isFileHandler', true)
}
setGlobalTrackers () {
// Align the text fields
const textFieldStyle = { width: '100%' }
const textareaStyle = { margin: 0 }
return (
<Preference>
<TextField
className='torrent-trackers control'
style={textFieldStyle}
textareaStyle={textareaStyle}
multiLine
rows={2}
rowsMax={10}
value={this.state.globalTrackers}
onChange={this.handleSetGlobalTrackers}
/>
</Preference>
)
}
handleSetGlobalTrackers (e, globalTrackers) {
this.setState({ globalTrackers })
const announceList = globalTrackers
.split('\n')
.map((s) => s.trim())
.filter((s) => s !== '')
dispatch('updatePreferences', 'globalTrackers', announceList)
dispatch('updateGlobalTrackers', announceList)
}
render () {
const style = {
color: colors.grey400,
marginLeft: 25,
marginRight: 25
}
return (
<div style={style}>
<PreferencesSection title='Folders'>
{this.downloadPathSelector()}
{this.autoAddTorrentsCheckbox()}
{this.torrentsFolderPathSelector()}
</PreferencesSection>
<PreferencesSection title='Playback'>
{this.openExternalPlayerCheckbox()}
{this.externalPlayerPathSelector()}
{this.highestPlaybackPriorityCheckbox()}
</PreferencesSection>
<PreferencesSection title='Default torrent app'>
{this.setDefaultAppButton()}
</PreferencesSection>
<PreferencesSection title='General'>
{this.setStartupCheckbox()}
{this.soundNotificationsCheckbox()}
</PreferencesSection>
<PreferencesSection title='Trackers'>
{this.setGlobalTrackers()}
</PreferencesSection>
</div>
)
}
}
class PreferencesSection extends React.Component {
static get propTypes () {
return {
title: PropTypes.string
}
}
render () {
const style = {
marginBottom: 25,
marginTop: 25
}
return (
<div style={style}>
<Heading level={2}>{this.props.title}</Heading>
{this.props.children}
</div>
)
}
}
class Preference extends React.Component {
render () {
const style = { marginBottom: 10 }
return (<div style={style}>{this.props.children}</div>)
}
}
module.exports = PreferencesPage

View File

@@ -1,15 +1,19 @@
const React = require('react')
const prettyBytes = require('prettier-bytes')
const Checkbox = require('material-ui/Checkbox').default
const LinearProgress = require('material-ui/LinearProgress').default
const TorrentSummary = require('../lib/torrent-summary')
const TorrentPlayer = require('../lib/torrent-player')
const {dispatcher} = require('../lib/dispatcher')
const { dispatcher } = require('../lib/dispatcher')
const { calculateEta } = require('../lib/time')
module.exports = class TorrentList extends React.Component {
render () {
var state = this.props.state
const state = this.props.state
var contents = []
const contents = []
if (state.downloadPathStatus === 'missing') {
contents.push(
<div key='torrent-missing-path'>
@@ -21,7 +25,7 @@ module.exports = class TorrentList extends React.Component {
</div>
)
}
var torrentElems = state.saved.torrents.map(
const torrentElems = state.saved.torrents.map(
(torrentSummary) => this.renderTorrent(torrentSummary)
)
contents.push(...torrentElems)
@@ -32,87 +36,138 @@ module.exports = class TorrentList extends React.Component {
)
return (
<div key='torrent-list' className='torrent-list'>
<div
key='torrent-list'
className='torrent-list'
onContextMenu={dispatcher('openTorrentListContextMenu')}
>
{contents}
</div>
)
}
renderTorrent (torrentSummary) {
var state = this.props.state
var infoHash = torrentSummary.infoHash
var isSelected = infoHash && state.selectedInfoHash === infoHash
const state = this.props.state
const infoHash = torrentSummary.infoHash
const isSelected = infoHash && state.selectedInfoHash === infoHash
// Background image: show some nice visuals, like a frame from the movie, if possible
var style = {}
const style = {}
if (torrentSummary.posterFileName) {
var gradient = isSelected
? 'linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 100%)'
: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%)'
var posterPath = TorrentSummary.getPosterPath(torrentSummary)
style.backgroundImage = gradient + `, url('${posterPath}')`
const gradient = 'linear-gradient(to bottom, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.4) 100%)'
const posterPath = TorrentSummary.getPosterPath(torrentSummary)
style.backgroundImage = `${gradient}, url('${posterPath}')`
}
// Foreground: name of the torrent, basic info like size, play button,
// cast buttons if available, and delete
var classes = ['torrent']
// playStatus turns the play button into a loading spinner or error icon
if (torrentSummary.playStatus) classes.push(torrentSummary.playStatus)
const classes = ['torrent']
if (isSelected) classes.push('selected')
if (!infoHash) classes.push('disabled')
if (!torrentSummary.torrentKey) throw new Error('Missing torrentKey')
return (
<div
id={torrentSummary.testID && ('torrent-' + torrentSummary.testID)}
key={torrentSummary.torrentKey}
style={style}
className={classes.join(' ')}
onContextMenu={infoHash && dispatcher('openTorrentContextMenu', infoHash)}
onClick={infoHash && dispatcher('toggleSelectTorrent', infoHash)}>
onClick={infoHash && dispatcher('toggleSelectTorrent', infoHash)}
>
{this.renderTorrentMetadata(torrentSummary)}
{infoHash ? this.renderTorrentButtons(torrentSummary) : null}
{isSelected ? this.renderTorrentDetails(torrentSummary) : null}
<hr />
</div>
)
}
// Show name, download status, % complete
renderTorrentMetadata (torrentSummary) {
var name = torrentSummary.name || 'Loading torrent...'
var elements = [(
const name = torrentSummary.name || 'Loading torrent...'
const elements = [(
<div key='name' className='name ellipsis'>{name}</div>
)]
// If it's downloading/seeding then show progress info
var prog = torrentSummary.progress
const prog = torrentSummary.progress
let progElems
if (torrentSummary.error) {
elements.push(
<div key='progress-info' className='ellipsis'>
{getErrorMessage(torrentSummary)}
</div>
)
progElems = [getErrorMessage(torrentSummary)]
} else if (torrentSummary.status !== 'paused' && prog) {
elements.push(
<div key='progress-info' className='ellipsis'>
{renderPercentProgress()}
{renderTotalProgress()}
{renderPeers()}
{renderDownloadSpeed()}
{renderUploadSpeed()}
{renderEta()}
progElems = [
renderDownloadCheckbox(),
renderTorrentStatus(),
renderProgressBar(),
renderPercentProgress(),
renderTotalProgress(),
renderPeers(),
renderSpeeds(),
renderEta()
]
} else {
progElems = [
renderDownloadCheckbox(),
renderTorrentStatus()
]
}
elements.push(
<div key='progress-info' className='ellipsis'>
{progElems}
</div>
)
return (<div key='metadata' className='metadata'>{elements}</div>)
function renderDownloadCheckbox () {
const infoHash = torrentSummary.infoHash
const isActive = ['downloading', 'seeding'].includes(torrentSummary.status)
return (
<Checkbox
key='download-button'
className={'control download ' + torrentSummary.status}
style={{
display: 'inline-block',
width: 32
}}
iconStyle={{
width: 20,
height: 20
}}
checked={isActive}
onClick={stopPropagation}
onCheck={dispatcher('toggleTorrent', infoHash)}
/>
)
}
function renderProgressBar () {
const progress = Math.floor(100 * prog.progress)
const styles = {
wrapper: {
display: 'inline-block',
marginRight: 8
},
progress: {
height: 8,
width: 30
}
}
return (
<div key='progress-bar' style={styles.wrapper}>
<LinearProgress style={styles.progress} mode='determinate' value={progress} />
</div>
)
}
return (<div key='metadata' className='metadata'>{elements}</div>)
function renderPercentProgress () {
var progress = Math.floor(100 * prog.progress)
const progress = Math.floor(100 * prog.progress)
return (<span key='percent-progress'>{progress}%</span>)
}
function renderTotalProgress () {
var downloaded = prettyBytes(prog.downloaded)
var total = prettyBytes(prog.length || 0)
const downloaded = prettyBytes(prog.downloaded)
const total = prettyBytes(prog.length || 0)
if (downloaded === total) {
return (<span key='total-progress'>{downloaded}</span>)
} else {
@@ -122,116 +177,78 @@ module.exports = class TorrentList extends React.Component {
function renderPeers () {
if (prog.numPeers === 0) return
var count = prog.numPeers === 1 ? 'peer' : 'peers'
const count = prog.numPeers === 1 ? 'peer' : 'peers'
return (<span key='peers'>{prog.numPeers} {count}</span>)
}
function renderDownloadSpeed () {
if (prog.downloadSpeed === 0) return
return (<span key='download'> {prettyBytes(prog.downloadSpeed)}/s</span>)
}
function renderUploadSpeed () {
if (prog.uploadSpeed === 0) return
return (<span key='upload'> {prettyBytes(prog.uploadSpeed)}/s</span>)
function renderSpeeds () {
let str = ''
if (prog.downloadSpeed > 0) str += ' ↓ ' + prettyBytes(prog.downloadSpeed) + '/s'
if (prog.uploadSpeed > 0) str += ' ↑ ' + prettyBytes(prog.uploadSpeed) + '/s'
if (str === '') return
return (<span key='download'>{str}</span>)
}
function renderEta () {
var downloaded = prog.downloaded
var total = prog.length || 0
var missing = total - downloaded
var downloadSpeed = prog.downloadSpeed
const downloaded = prog.downloaded
const total = prog.length || 0
const missing = total - downloaded
const downloadSpeed = prog.downloadSpeed
if (downloadSpeed === 0 || missing === 0) return
var rawEta = missing / downloadSpeed
var hours = Math.floor(rawEta / 3600) % 24
var minutes = Math.floor(rawEta / 60) % 60
var seconds = Math.floor(rawEta % 60)
const etaStr = calculateEta(missing, downloadSpeed)
// Only display hours and minutes if they are greater than 0 but always
// display minutes if hours is being displayed
var hoursStr = hours ? hours + 'h' : ''
var minutesStr = (hours || minutes) ? minutes + 'm' : ''
var secondsStr = seconds + 's'
return (<span key='eta'>{etaStr}</span>)
}
return (<span>ETA: {hoursStr} {minutesStr} {secondsStr}</span>)
function renderTorrentStatus () {
let status
if (torrentSummary.status === 'paused') {
if (!torrentSummary.progress) status = ''
else if (torrentSummary.progress.progress === 1) status = 'Not seeding'
else status = 'Paused'
} else if (torrentSummary.status === 'downloading') {
if (!torrentSummary.progress) status = ''
else if (!torrentSummary.progress.ready) status = 'Verifying'
else status = 'Downloading'
} else if (torrentSummary.status === 'seeding') {
status = 'Seeding'
} else { // torrentSummary.status is 'new' or something unexpected
status = ''
}
return (<span key='torrent-status'>{status}</span>)
}
}
// Download button toggles between torrenting (DL/seed) and paused
// Play button starts streaming the torrent immediately, unpausing if needed
renderTorrentButtons (torrentSummary) {
var infoHash = torrentSummary.infoHash
const infoHash = torrentSummary.infoHash
var playIcon, playTooltip, playClass
if (torrentSummary.playStatus === 'timeout') {
playIcon = 'warning'
playTooltip = 'Playback timed out. No seeds? No internet? Click to try again.'
} else {
playIcon = 'play_arrow'
playTooltip = 'Start streaming'
}
var downloadIcon, downloadTooltip
if (torrentSummary.status === 'seeding') {
downloadIcon = 'file_upload'
downloadTooltip = 'Seeding. Click to stop.'
} else if (torrentSummary.status === 'downloading') {
downloadIcon = 'file_download'
downloadTooltip = 'Torrenting. Click to stop.'
} else {
downloadIcon = 'file_download'
downloadTooltip = 'Click to start torrenting.'
}
// Do we have a saved position? Show it using a radial progress bar on top
// of the play button, unless already showing a spinner there:
var positionElem
var willShowSpinner = torrentSummary.playStatus === 'requested'
var defaultFile = torrentSummary.files &&
torrentSummary.files[torrentSummary.defaultPlayFileIndex]
if (defaultFile && defaultFile.currentTime && !willShowSpinner) {
var fraction = defaultFile.currentTime / defaultFile.duration
positionElem = this.renderRadialProgressBar(fraction, 'radial-progress-large')
playClass = 'resume-position'
}
// Only show the play button for torrents that contain playable media
var playButton, downloadButton
var noErrors = !torrentSummary.error
if (noErrors && TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) {
// Only show the play/dowload buttons for torrents that contain playable media
let playButton
if (!torrentSummary.error && TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) {
playButton = (
<i
key='play-button'
title={playTooltip}
className={'button-round icon play ' + playClass}
onClick={dispatcher('playFile', infoHash)}>
{playIcon}
</i>
)
}
if (noErrors) {
downloadButton = (
<i
key='download-button'
className={'button-round icon download ' + torrentSummary.status}
title={downloadTooltip}
onClick={dispatcher('toggleTorrent', infoHash)}>
{downloadIcon}
title='Start streaming'
className='icon play'
onClick={dispatcher('playFile', infoHash)}
>
play_circle_outline
</i>
)
}
return (
<div key='buttons' className='buttons'>
{positionElem}
<div className='torrent-controls'>
{playButton}
{downloadButton}
<i
key='delete-button'
className='icon delete'
title='Remove torrent'
onClick={dispatcher('confirmDeleteTorrent', infoHash, false)}>
onClick={dispatcher('confirmDeleteTorrent', infoHash, false)}
>
close
</i>
</div>
@@ -240,9 +257,9 @@ module.exports = class TorrentList extends React.Component {
// Show files, per-file download status and play buttons, and so on
renderTorrentDetails (torrentSummary) {
var filesElement
let filesElement
if (torrentSummary.error || !torrentSummary.files) {
var message = ''
let message = ''
if (torrentSummary.error === 'path-missing') {
// Special case error: this torrent's download dir or file is missing
message = 'Missing path: ' + TorrentSummary.getFileOrFolder(torrentSummary)
@@ -263,15 +280,17 @@ module.exports = class TorrentList extends React.Component {
)
} else {
// We do know the files. List them and show download stats for each one
var fileRows = torrentSummary.files
const sortByName = this.props.state.saved.prefs.sortByName
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
let fileRows = torrentSummary.files
.filter((file) => !file.path.includes('/.____padding_file/'))
.map((file, index) => ({ file, index }))
.sort(function (a, b) {
if (a.file.name < b.file.name) return -1
if (b.file.name < a.file.name) return 1
return 0
})
.map((object) => this.renderFileRow(torrentSummary, object.file, object.index))
if (sortByName) {
fileRows = fileRows.sort((a, b) => collator.compare(a.file.name, b.file.name))
}
fileRows = fileRows.map((obj) => this.renderFileRow(torrentSummary, obj.file, obj.index))
filesElement = (
<div key='files' className='files'>
@@ -295,36 +314,39 @@ module.exports = class TorrentList extends React.Component {
renderFileRow (torrentSummary, file, index) {
// First, find out how much of the file we've downloaded
// Are we even torrenting it?
var isSelected = torrentSummary.selections && torrentSummary.selections[index]
var isDone = false // Are we finished torrenting it?
var progress = ''
const isSelected = torrentSummary.selections && torrentSummary.selections[index]
let isDone = false // Are we finished torrenting it?
let progress = ''
if (torrentSummary.progress && torrentSummary.progress.files &&
torrentSummary.progress.files[index]) {
var fileProg = torrentSummary.progress.files[index]
const fileProg = torrentSummary.progress.files[index]
isDone = fileProg.numPiecesPresent === fileProg.numPieces
progress = Math.round(100 * fileProg.numPiecesPresent / fileProg.numPieces) + '%'
progress = Math.floor(100 * fileProg.numPiecesPresent / fileProg.numPieces) + '%'
}
// Second, for media files where we saved our position, show how far we got
var positionElem
let positionElem
if (file.currentTime) {
// Radial progress bar. 0% = start from 0:00, 270% = 3/4 of the way thru
positionElem = this.renderRadialProgressBar(file.currentTime / file.duration)
}
// Finally, render the file as a table row
var isPlayable = TorrentPlayer.isPlayable(file)
var infoHash = torrentSummary.infoHash
var icon
var handleClick
const isPlayable = TorrentPlayer.isPlayable(file)
const infoHash = torrentSummary.infoHash
let icon
let handleClick
if (isPlayable) {
icon = 'play_arrow' /* playable? add option to play */
handleClick = dispatcher('playFile', infoHash, index)
} else {
icon = 'description' /* file icon, opens in OS default app */
handleClick = dispatcher('openItem', infoHash, index)
handleClick = isDone
? dispatcher('openPath', infoHash, index)
: (e) => e.stopPropagation() // noop if file is not ready
}
var rowClass = ''
// TODO: add a css 'disabled' class to indicate that a file cannot be opened/streamed
let rowClass = ''
if (!isSelected) rowClass = 'disabled' // File deselected, not being torrented
if (!isDone && !isPlayable) rowClass = 'disabled' // Can't open yet, can't stream
return (
@@ -342,41 +364,47 @@ module.exports = class TorrentList extends React.Component {
<td className={'col-size ' + rowClass}>
{prettyBytes(file.length)}
</td>
<td className='col-select'
onClick={dispatcher('toggleTorrentFile', infoHash, index)}>
<i className='icon'>{isSelected ? 'close' : 'add'}</i>
<td
className='col-select'
onClick={dispatcher('toggleTorrentFile', infoHash, index)}
>
<i className='icon deselect-file'>{isSelected ? 'close' : 'add'}</i>
</td>
</tr>
)
}
renderRadialProgressBar (fraction, cssClass) {
var rotation = 360 * fraction
var transformFill = {transform: 'rotate(' + (rotation / 2) + 'deg)'}
var transformFix = {transform: 'rotate(' + rotation + 'deg)'}
const rotation = 360 * fraction
const transformFill = { transform: 'rotate(' + (rotation / 2) + 'deg)' }
const transformFix = { transform: 'rotate(' + rotation + 'deg)' }
return (
<div key='radial-progress' className={'radial-progress ' + cssClass}>
<div key='circle' className='circle'>
<div key='mask-full' className='mask full' style={transformFill}>
<div key='fill' className='fill' style={transformFill} />
<div className='circle'>
<div className='mask full' style={transformFill}>
<div className='fill' style={transformFill} />
</div>
<div key='mask-half' className='mask half'>
<div key='fill' className='fill' style={transformFill} />
<div key='fill-fix' className='fill fix' style={transformFix} />
<div className='mask half'>
<div className='fill' style={transformFill} />
<div className='fill fix' style={transformFix} />
</div>
</div>
<div key='inset' className='inset' />
<div className='inset' />
</div>
)
}
}
function stopPropagation (e) {
e.stopPropagation()
}
function getErrorMessage (torrentSummary) {
var err = torrentSummary.error
const err = torrentSummary.error
if (err === 'path-missing') {
return (
<span>
<span key='path-missing'>
Path missing.<br />
Fix and restart the app, or delete the torrent.
</span>

View File

@@ -1,97 +0,0 @@
const React = require('react')
const Header = require('./header')
const Views = {
'home': require('./torrent-list'),
'player': require('./player'),
'create-torrent': require('./create-torrent'),
'preferences': require('./preferences')
}
const Modals = {
'open-torrent-address-modal': require('./open-torrent-address-modal'),
'remove-torrent-modal': require('./remove-torrent-modal'),
'update-available-modal': require('./update-available-modal'),
'unsupported-media-modal': require('./unsupported-media-modal')
}
module.exports = class App extends React.Component {
constructor (props) {
super(props)
this.state = props.state
}
render () {
var state = this.state
// Hide player controls while playing video, if the mouse stays still for a while
// Never hide the controls when:
// * The mouse is over the controls or we're scrubbing (see CSS)
// * The video is paused
// * The video is playing remotely on Chromecast or Airplay
var hideControls = state.location.url() === 'player' &&
state.playing.mouseStationarySince !== 0 &&
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
!state.playing.isPaused &&
state.playing.location === 'local' &&
state.playing.playbackRate === 1
var cls = [
'view-' + state.location.url(), /* e.g. view-home, view-player */
'is-' + process.platform /* e.g. is-darwin, is-win32, is-linux */
]
if (state.window.isFullScreen) cls.push('is-fullscreen')
if (state.window.isFocused) cls.push('is-focused')
if (hideControls) cls.push('hide-video-controls')
var vdom = (
<div className={'app ' + cls.join(' ')}>
<Header state={state} />
{this.getErrorPopover()}
<div key='content' className='content'>{this.getView()}</div>
{this.getModal()}
</div>
)
return vdom
}
getErrorPopover () {
var now = new Date().getTime()
var recentErrors = this.state.errors.filter((x) => now - x.time < 5000)
var hasErrors = recentErrors.length > 0
var errorElems = recentErrors.map(function (error, i) {
return (<div key={i} className='error'>{error.message}</div>)
})
return (
<div key='errors'
className={'error-popover ' + (hasErrors ? 'visible' : 'hidden')}>
<div key='title' className='title'>Error</div>
{errorElems}
</div>
)
}
getModal () {
var state = this.state
if (!state.modal) return
var ModalContents = Modals[state.modal.id]
return (
<div key='modal' className='modal'>
<div key='modal-background' className='modal-background' />
<div key='modal-content' className='modal-content'>
<ModalContents state={state} />
</div>
</div>
)
}
getView () {
var state = this.state
var View = Views[state.location.url()]
return (<View state={state} />)
}
}

View File

@@ -1,131 +0,0 @@
const React = require('react')
const createTorrent = require('create-torrent')
const path = require('path')
const prettyBytes = require('prettier-bytes')
const {dispatch, dispatcher} = require('../lib/dispatcher')
const CreateTorrentErrorPage = require('./create-torrent-error-page')
module.exports = class CreateTorrentPage extends React.Component {
render () {
var state = this.props.state
var info = state.location.current()
// Preprocess: exclude .DS_Store and other dotfiles
var files = info.files
.filter((f) => !f.name.startsWith('.'))
.map((f) => ({name: f.name, path: f.path, size: f.size}))
if (files.length === 0) return (<CreateTorrentErrorPage state={state} />)
// First, extract the base folder that the files are all in
var pathPrefix = info.folderPath
if (!pathPrefix) {
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
pathPrefix = path.dirname(pathPrefix)
}
}
// Sanity check: show the number of files and total size
var numFiles = files.length
var totalBytes = files
.map((f) => f.size)
.reduce((a, b) => a + b, 0)
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
// Then, use the name of the base folder (or sole file, for a single file torrent)
// as the default name. Show all files relative to the base folder.
var defaultName, basePath
if (files.length === 1) {
// Single file torrent: /a/b/foo.jpg -> torrent name 'foo.jpg', path '/a/b'
defaultName = files[0].name
basePath = pathPrefix
} else {
// Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name 'b', path '/a'
defaultName = path.basename(pathPrefix)
basePath = path.dirname(pathPrefix)
}
var maxFileElems = 100
var fileElems = files.slice(0, maxFileElems).map(function (file, i) {
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
return (<div key={i}>{relativePath}</div>)
})
if (files.length > maxFileElems) {
fileElems.push(<div key='more'>+ {maxFileElems - files.length} more</div>)
}
var trackers = createTorrent.announceList.join('\n')
var collapsedClass = info.showAdvanced ? 'expanded' : 'collapsed'
return (
<div className='create-torrent'>
<h2>Create torrent {defaultName}</h2>
<div key='info' className='torrent-info'>
{torrentInfo}
</div>
<div key='path-prefix' className='torrent-attribute'>
<label>Path:</label>
<div className='torrent-attribute'>{pathPrefix}</div>
</div>
<div key='toggle' className={'expand-collapse ' + collapsedClass}
onClick={dispatcher('toggleCreateTorrentAdvanced')}>
{info.showAdvanced ? 'Basic' : 'Advanced'}
</div>
<div key='advanced' className={'create-torrent-advanced ' + collapsedClass}>
<div key='comment' className='torrent-attribute'>
<label>Comment:</label>
<textarea className='torrent-attribute torrent-comment' />
</div>
<div key='trackers' className='torrent-attribute'>
<label>Trackers:</label>
<textarea className='torrent-attribute torrent-trackers' defaultValue={trackers} />
</div>
<div key='private' className='torrent-attribute'>
<label>Private:</label>
<input type='checkbox' className='torrent-is-private' value='torrent-is-private' />
</div>
<div key='files' className='torrent-attribute'>
<label>Files:</label>
<div>{fileElems}</div>
</div>
</div>
<div key='buttons' className='float-right'>
<button key='cancel' className='button-flat light' onClick={dispatcher('cancel')}>Cancel</button>
<button key='create' className='button-raised' onClick={handleOK}>Create Torrent</button>
</div>
</div>
)
function handleOK () {
// TODO: dcposch use React refs instead
var announceList = document.querySelector('.torrent-trackers').value
.split('\n')
.map((s) => s.trim())
.filter((s) => s !== '')
var isPrivate = document.querySelector('.torrent-is-private').checked
var comment = document.querySelector('.torrent-comment').value.trim()
var options = {
// We can't let the user choose their own name if we want WebTorrent
// to use the files in place rather than creating a new folder.
// If we ever want to add support for that:
// name: document.querySelector('.torrent-name').value
name: defaultName,
path: basePath,
files: files,
announce: announceList,
private: isPrivate,
comment: comment
}
dispatch('createTorrent', options)
}
}
}
// Finds the longest common prefix
function findCommonPrefix (a, b) {
for (var i = 0; i < a.length && i < b.length; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
}
if (i === a.length) return a
if (i === b.length) return b
return a.substring(0, i)
}

View File

@@ -1,32 +0,0 @@
const React = require('react')
const {dispatch, dispatcher} = require('../lib/dispatcher')
module.exports = class OpenTorrentAddressModal extends React.Component {
render () {
// TODO: dcposch remove janky inline <script>
return (
<div className='open-torrent-address-modal'>
<p><label>Enter torrent address or magnet link</label></p>
<p>
<input id='add-torrent-url' type='text' onKeyPress={handleKeyPress} />
</p>
<p className='float-right'>
<button className='button button-flat' onClick={dispatcher('exitModal')}>Cancel</button>
<button className='button button-raised' onClick={handleOK}>OK</button>
</p>
<script>document.querySelector('#add-torrent-url').focus()</script>
</div>
)
}
}
function handleKeyPress (e) {
if (e.which === 13) handleOK() /* hit Enter to submit */
}
function handleOK () {
dispatch('exitModal')
// TODO: dcposch use React refs instead
dispatch('addTorrent', document.querySelector('#add-torrent-url').value)
}

View File

@@ -1,627 +0,0 @@
const React = require('react')
const Bitfield = require('bitfield')
const prettyBytes = require('prettier-bytes')
const zeroFill = require('zero-fill')
const TorrentSummary = require('../lib/torrent-summary')
const {dispatch, dispatcher} = require('../lib/dispatcher')
// Shows a streaming video player. Standard features + Chromecast + Airplay
module.exports = class Player extends React.Component {
render () {
// Show the video as large as will fit in the window, play immediately
// If the video is on Chromecast or Airplay, show a title screen instead
var state = this.props.state
var showVideo = state.playing.location === 'local'
return (
<div
className='player'
onWheel={handleVolumeWheel}
onMouseMove={dispatcher('mediaMouseMoved')}>
{showVideo ? renderMedia(state) : renderCastScreen(state)}
{renderPlayerControls(state)}
</div>
)
}
}
// Handles volume change by wheel
function handleVolumeWheel (e) {
dispatch('changeVolume', (-e.deltaY | e.deltaX) / 500)
}
function renderMedia (state) {
if (!state.server) return
// Unfortunately, play/pause can't be done just by modifying HTML.
// Instead, grab the DOM node and play/pause it if necessary
// Get the <video> or <audio> tag
var mediaElement = document.querySelector(state.playing.type)
if (mediaElement !== null) {
if (state.playing.isPaused && !mediaElement.paused) {
mediaElement.pause()
} else if (!state.playing.isPaused && mediaElement.paused) {
mediaElement.play()
}
// When the user clicks or drags on the progress bar, jump to that position
if (state.playing.jumpToTime != null) {
mediaElement.currentTime = state.playing.jumpToTime
state.playing.jumpToTime = null
}
if (state.playing.playbackRate !== mediaElement.playbackRate) {
mediaElement.playbackRate = state.playing.playbackRate
}
// Recover previous volume
if (state.previousVolume !== null && isFinite(state.previousVolume)) {
mediaElement.volume = state.previousVolume
state.previousVolume = null
}
// Set volume
if (state.playing.setVolume !== null && isFinite(state.playing.setVolume)) {
mediaElement.volume = state.playing.setVolume
state.playing.setVolume = null
}
// Switch to the newly added subtitle track, if available
var tracks = mediaElement.textTracks || []
for (var j = 0; j < tracks.length; j++) {
var isSelectedTrack = j === state.playing.subtitles.selectedIndex
tracks[j].mode = isSelectedTrack ? 'showing' : 'hidden'
}
// Save video position
var file = state.getPlayingFileSummary()
file.currentTime = state.playing.currentTime = mediaElement.currentTime
file.duration = state.playing.duration = mediaElement.duration
// Save selected subtitle
if (state.playing.subtitles.selectedIndex !== -1) {
var index = state.playing.subtitles.selectedIndex
file.selectedSubtitle = state.playing.subtitles.tracks[index].filePath
} else if (file.selectedSubtitle != null) {
delete file.selectedSubtitle
}
state.playing.volume = mediaElement.volume
}
// Add subtitles to the <video> tag
var trackTags = []
if (state.playing.subtitles.selectedIndex >= 0) {
for (var i = 0; i < state.playing.subtitles.tracks.length; i++) {
var track = state.playing.subtitles.tracks[i]
var isSelected = state.playing.subtitles.selectedIndex === i
trackTags.push(
<track
key={i}
default={isSelected ? 'default' : ''}
label={track.label}
type='subtitles'
src={track.buffer} />
)
}
}
// Create the <audio> or <video> tag
var MediaTagName = state.playing.type
var mediaTag = (
<MediaTagName
src={state.server.localURL}
onDoubleClick={dispatcher('toggleFullScreen')}
onLoadedMetadata={onLoadedMetadata}
onEnded={onEnded}
onStalled={dispatcher('mediaStalled')}
onError={dispatcher('mediaError')}
onTimeUpdate={dispatcher('mediaTimeUpdate')}
onEncrypted={dispatcher('mediaEncrypted')}
onCanPlay={onCanPlay}>
{trackTags}
</MediaTagName>
)
// Show the media.
return (
<div
key='letterbox'
className='letterbox'
onMouseMove={dispatcher('mediaMouseMoved')}>
{mediaTag}
{renderOverlay(state)}
</div>
)
// As soon as we know the video dimensions, resize the window
function onLoadedMetadata (e) {
if (state.playing.type !== 'video') return
var video = e.target
var dimensions = {
width: video.videoWidth,
height: video.videoHeight
}
dispatch('setDimensions', dimensions)
}
// When the video completes, pause the video instead of looping
function onEnded (e) {
state.playing.isPaused = true
}
function onCanPlay (e) {
var elem = e.target
if (state.playing.type === 'video' &&
elem.webkitVideoDecodedByteCount === 0) {
dispatch('mediaError', 'Video codec unsupported')
} else if (elem.webkitAudioDecodedByteCount === 0) {
dispatch('mediaError', 'Audio codec unsupported')
} else {
dispatch('mediaSuccess')
elem.play()
}
}
}
function renderOverlay (state) {
var elems = []
var audioMetadataElem = renderAudioMetadata(state)
var spinnerElem = renderLoadingSpinner(state)
if (audioMetadataElem) elems.push(audioMetadataElem)
if (spinnerElem) elems.push(spinnerElem)
// Video fills the window, centered with black bars if necessary
// Audio gets a static poster image and a summary of the file metadata.
var style
if (state.playing.type === 'audio') {
style = { backgroundImage: cssBackgroundImagePoster(state) }
} else if (elems.length !== 0) {
style = { backgroundImage: cssBackgroundImageDarkGradient() }
} else {
// Video playing, so no spinner. No overlay needed
return
}
return (
<div key='overlay' className='media-overlay-background' style={style}>
<div className='media-overlay'>{elems}</div>
</div>
)
}
function renderAudioMetadata (state) {
var fileSummary = state.getPlayingFileSummary()
if (!fileSummary.audioInfo) return
var info = fileSummary.audioInfo
// Get audio track info
var title = info.title
if (!title) {
title = fileSummary.name
}
var artist = info.artist && info.artist[0]
var album = info.album
if (album && info.year && !album.includes(info.year)) {
album += ' (' + info.year + ')'
}
var track
if (info.track && info.track.no && info.track.of) {
track = info.track.no + ' of ' + info.track.of
}
// Show a small info box in the middle of the screen with title/album/etc
var elems = []
if (artist) {
elems.push((
<div key='artist' className='audio-artist'>
<label>Artist</label>{artist}
</div>
))
}
if (album) {
elems.push((
<div key='album' className='audio-album'>
<label>Album</label>{album}
</div>
))
}
if (track) {
elems.push((
<div key='track' className='audio-track'>
<label>Track</label>{track}
</div>
))
}
// Align the title with the other info, if available. Otherwise, center title
var emptyLabel = (<label />)
elems.unshift((
<div key='title' className='audio-title'>
{elems.length ? emptyLabel : undefined}{title}
</div>
))
return (<div key='audio-metadata' className='audio-metadata'>{elems}</div>)
}
function renderLoadingSpinner (state) {
if (state.playing.isPaused) return
var isProbablyStalled = state.playing.isStalled ||
(new Date().getTime() - state.playing.lastTimeUpdate > 2000)
if (!isProbablyStalled) return
var prog = state.getPlayingTorrentSummary().progress || {}
var fileProgress = 0
if (prog.files) {
var file = prog.files[state.playing.fileIndex]
fileProgress = Math.floor(100 * file.numPiecesPresent / file.numPieces)
}
return (
<div key='loading' className='media-stalled'>
<div key='loading-spinner' className='loading-spinner'>&nbsp;</div>
<div key='loading-progress' className='loading-status ellipsis'>
<span className='progress'>{fileProgress}%</span> downloaded,
<span> {prettyBytes(prog.downloadSpeed || 0)}/s</span>
<span> {prettyBytes(prog.uploadSpeed || 0)}/s</span>
</div>
</div>
)
}
function renderCastScreen (state) {
var castIcon, castType, isCast
if (state.playing.location.startsWith('chromecast')) {
castIcon = 'cast_connected'
castType = 'Chromecast'
isCast = true
} else if (state.playing.location.startsWith('airplay')) {
castIcon = 'airplay'
castType = 'AirPlay'
isCast = true
} else if (state.playing.location.startsWith('dlna')) {
castIcon = 'tv'
castType = 'DLNA'
isCast = true
} else if (state.playing.location === 'vlc') {
castIcon = 'tv'
castType = 'VLC'
isCast = false
} else if (state.playing.location === 'error') {
castIcon = 'error_outline'
castType = 'Error'
isCast = false
}
var isStarting = state.playing.location.endsWith('-pending')
var castName = state.playing.castName
var castStatus
if (isCast && isStarting) castStatus = 'Connecting to ' + castName + '...'
else if (isCast && !isStarting) castStatus = 'Connected to ' + castName
else castStatus = ''
// Show a nice title image, if possible
var style = {
backgroundImage: cssBackgroundImagePoster(state)
}
return (
<div key='cast' className='letterbox' style={style}>
<div className='cast-screen'>
<i className='icon'>{castIcon}</i>
<div key='type' className='cast-type'>{castType}</div>
<div key='status' className='cast-status'>{castStatus}</div>
</div>
</div>
)
}
function renderCastOptions (state) {
if (!state.devices.castMenu) return
var {location, devices} = state.devices.castMenu
var player = state.devices[location]
var items = devices.map(function (device, ix) {
var isSelected = player.device === device
var name = device.name
return (
<li key={ix} onClick={dispatcher('selectCastDevice', ix)}>
<i className='icon'>{isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
{name}
</li>
)
})
return (
<ul key='cast-options' className='options-list'>
{items}
</ul>
)
}
function renderSubtitleOptions (state) {
var subtitles = state.playing.subtitles
if (!subtitles.tracks.length || !subtitles.showMenu) return
var items = subtitles.tracks.map(function (track, ix) {
var isSelected = state.playing.subtitles.selectedIndex === ix
return (
<li key={ix} onClick={dispatcher('selectSubtitle', ix)}>
<i className='icon'>{'radio_button_' + (isSelected ? 'checked' : 'unchecked')}</i>
{track.label}
</li>
)
})
var noneSelected = state.playing.subtitles.selectedIndex === -1
var noneClass = 'radio_button_' + (noneSelected ? 'checked' : 'unchecked')
return (
<ul key='subtitle-options' className='options-list'>
{items}
<li onClick={dispatcher('selectSubtitle', -1)}>
<i className='icon'>{noneClass}</i>
None
</li>
</ul>
)
}
function renderPlayerControls (state) {
var positionPercent = 100 * state.playing.currentTime / state.playing.duration
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 3px)' }
var captionsClass = state.playing.subtitles.tracks.length === 0
? 'disabled'
: state.playing.subtitles.selectedIndex >= 0
? 'active'
: ''
var elements = [
<div key='playback-bar' className='playback-bar'>
{renderLoadingBar(state)}
<div
key='cursor'
className='playback-cursor'
style={playbackCursorStyle}
/>
<div
key='scrub-bar'
className='scrub-bar'
draggable='true'
onDragStart={handleDragStart}
onClick={handleScrub}
onDrag={handleScrub}
/>
</div>,
<i
key='play'
className='icon play-pause float-left'
onClick={dispatcher('playPause')}>
{state.playing.isPaused ? 'play_arrow' : 'pause'}
</i>,
<i
key='fullscreen'
className='icon fullscreen float-right'
onClick={dispatcher('toggleFullScreen')}>
{state.window.isFullScreen ? 'fullscreen_exit' : 'fullscreen'}
</i>
]
if (state.playing.type === 'video') {
// show closed captions icon
elements.push((
<i
key='subtitles'
className={'icon closed-caption float-right ' + captionsClass}
onClick={handleSubtitles}>
closed_caption
</i>
))
}
// If we've detected a Chromecast or AppleTV, the user can play video there
var castTypes = ['chromecast', 'airplay', 'dlna']
var isCastingAnywhere = castTypes.some(
(castType) => state.playing.location.startsWith(castType))
// Add the cast buttons. Icons for each cast type, connected/disconnected:
var buttonIcons = {
'chromecast': {true: 'cast_connected', false: 'cast'},
'airplay': {true: 'airplay', false: 'airplay'},
'dlna': {true: 'tv', false: 'tv'}
}
castTypes.forEach(function (castType) {
// Do we show this button (eg. the Chromecast button) at all?
var isCasting = state.playing.location.startsWith(castType)
var player = state.devices[castType]
if ((!player || player.getDevices().length === 0) && !isCasting) return
// Show the button. Three options for eg the Chromecast button:
var buttonClass, buttonHandler
if (isCasting) {
// Option 1: we are currently connected to Chromecast. Button stops the cast.
buttonClass = 'active'
buttonHandler = dispatcher('stopCasting')
} else if (isCastingAnywhere) {
// Option 2: we are currently connected somewhere else. Button disabled.
buttonClass = 'disabled'
buttonHandler = undefined
} else {
// Option 3: we are not connected anywhere. Button opens Chromecast menu.
buttonClass = ''
buttonHandler = dispatcher('toggleCastMenu', castType)
}
var buttonIcon = buttonIcons[castType][isCasting]
elements.push((
<i
key={castType}
className={'icon device float-right ' + buttonClass}
onClick={buttonHandler}>
{buttonIcon}
</i>
))
})
// Render volume slider
var volume = state.playing.volume
var volumeIcon = 'volume_' + (
volume === 0 ? 'off'
: volume < 0.3 ? 'mute'
: volume < 0.6 ? 'down'
: 'up')
var volumeStyle = {
background: '-webkit-gradient(linear, left top, right top, ' +
'color-stop(' + (volume * 100) + '%, #eee), ' +
'color-stop(' + (volume * 100) + '%, #727272))'
}
// TODO: dcposch change the range input to use value / onChanged instead of
// "readonly" / onMouse[Down,Move,Up]
elements.push((
<div key='volume' className='volume float-left'>
<i
className='icon volume-icon float-left'
onMouseDown={handleVolumeMute}>
{volumeIcon}
</i>
<input
className='volume-slider float-right'
type='range' min='0' max='1' step='0.05'
value={volume}
onChange={handleVolumeScrub}
style={volumeStyle}
/>
</div>
))
// Show video playback progress
var currentTimeStr = formatTime(state.playing.currentTime)
var durationStr = formatTime(state.playing.duration)
elements.push((
<span key='time' className='time float-left'>
{currentTimeStr} / {durationStr}
</span>
))
// render playback rate
if (state.playing.playbackRate !== 1) {
elements.push((
<span key='rate' className='rate float-left'>
{state.playing.playbackRate}x
</span>
))
}
return (
<div key='controls' className='controls'>
{elements}
{renderCastOptions(state)}
{renderSubtitleOptions(state)}
</div>
)
function handleDragStart (e) {
// Prevent the cursor from changing, eg to a green + icon on Mac
if (e.dataTransfer) {
var dt = e.dataTransfer
dt.effectAllowed = 'none'
}
}
// Handles a click or drag to scrub (jump to another position in the video)
function handleScrub (e) {
if (!e.clientX) return
dispatch('mediaMouseMoved')
var windowWidth = document.querySelector('body').clientWidth
var fraction = e.clientX / windowWidth
var position = fraction * state.playing.duration /* seconds */
dispatch('skipTo', position)
}
// Handles volume muting and Unmuting
function handleVolumeMute (e) {
if (state.playing.volume === 0.0) {
dispatch('setVolume', 1.0)
} else {
dispatch('setVolume', 0.0)
}
}
// Handles volume slider scrub
function handleVolumeScrub (e) {
dispatch('setVolume', e.target.value)
}
function handleSubtitles (e) {
if (!state.playing.subtitles.tracks.length || e.ctrlKey || e.metaKey) {
// if no subtitles available select it
dispatch('openSubtitles')
} else {
dispatch('toggleSubtitlesMenu')
}
}
}
// Renders the loading bar. Shows which parts of the torrent are loaded, which
// can be 'spongey' / non-contiguous
function renderLoadingBar (state) {
var torrentSummary = state.getPlayingTorrentSummary()
if (!torrentSummary.progress) {
return []
}
// Find all contiguous parts of the torrent which are loaded
var prog = torrentSummary.progress
var fileProg = prog.files[state.playing.fileIndex]
var parts = []
var lastPiecePresent = false
for (var i = fileProg.startPiece; i <= fileProg.endPiece; i++) {
var partPresent = Bitfield.prototype.get.call(prog.bitfield, i)
if (partPresent && !lastPiecePresent) {
parts.push({start: i - fileProg.startPiece, count: 1})
} else if (partPresent) {
parts[parts.length - 1].count++
}
lastPiecePresent = partPresent
}
// Output some bars to show which parts of the file are loaded
var loadingBarElems = parts.map(function (part, i) {
var style = {
left: (100 * part.start / fileProg.numPieces) + '%',
width: (100 * part.count / fileProg.numPieces) + '%'
}
return (<div key={i} className='loading-bar-part' style={style} />)
})
return (<div key='loading-bar' className='loading-bar'>{loadingBarElems}</div>)
}
// Returns the CSS background-image string for a poster image + dark vignette
function cssBackgroundImagePoster (state) {
var torrentSummary = state.getPlayingTorrentSummary()
var posterPath = TorrentSummary.getPosterPath(torrentSummary)
if (!posterPath) return ''
return cssBackgroundImageDarkGradient() + `, url(${posterPath})`
}
function cssBackgroundImageDarkGradient () {
return 'radial-gradient(circle at center, ' +
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)'
}
function formatTime (time) {
if (typeof time !== 'number' || Number.isNaN(time)) {
return '0:00'
}
var hours = Math.floor(time / 3600)
var minutes = Math.floor(time % 3600 / 60)
if (hours > 0) {
minutes = zeroFill(2, minutes)
}
var seconds = zeroFill(2, Math.floor(time % 60))
return (hours > 0 ? hours + ':' : '') + minutes + ':' + seconds
}

View File

@@ -1,194 +0,0 @@
const React = require('react')
const remote = require('electron').remote
const dialog = remote.dialog
const {dispatch} = require('../lib/dispatcher')
module.exports = class Preferences extends React.Component {
render () {
var state = this.props.state
return (
<div className='preferences'>
{renderGeneralSection(state)}
{renderPlaybackSection(state)}
</div>
)
}
}
function renderGeneralSection (state) {
return renderSection({
key: 'general',
title: 'General',
description: '',
icon: 'settings'
}, [
renderDownloadPathSelector(state),
renderFileHandlers(state)
])
}
function renderPlaybackSection (state) {
return renderSection({
title: 'Playback',
description: '',
icon: 'settings'
}, [
renderPlayInVlcSelector(state)
])
}
function renderPlayInVlcSelector (state) {
return renderCheckbox({
key: 'play-in-vlc',
label: 'Play in VLC',
description: 'Media will play in VLC',
property: 'playInVlc',
value: state.saved.prefs.playInVlc
},
state.unsaved.prefs.playInVlc,
function (value) {
dispatch('updatePreferences', 'playInVlc', value)
})
}
function renderDownloadPathSelector (state) {
return renderFileSelector({
key: 'download-path',
label: 'Download Path',
description: 'Data from torrents will be saved here',
property: 'downloadPath',
options: {
title: 'Select download directory',
properties: [ 'openDirectory' ]
}
},
state.unsaved.prefs.downloadPath,
function (filePath) {
dispatch('updatePreferences', 'downloadPath', filePath)
})
}
function renderFileHandlers (state) {
var definition = {
key: 'file-handlers',
label: 'Handle Torrent Files'
}
var buttonText = state.unsaved.prefs.isFileHandler
? 'Remove default app for torrent files'
: 'Make WebTorrent the default app for torrent files'
var controls = [(
<button key='toggle-handlers'
className='btn'
onClick={toggleFileHandlers}>
{buttonText}
</button>
)]
return renderControlGroup(definition, controls)
function toggleFileHandlers () {
var isFileHandler = state.unsaved.prefs.isFileHandler
dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
}
}
// Renders a prefs section.
// - definition should be {icon, title, description}
// - controls should be an array of vdom elements
function renderSection (definition, controls) {
var helpElem = !definition.description ? null : (
<div key='help' className='help text'>
<i className='icon'>help_outline</i>{definition.description}
</div>
)
return (
<section key={definition.key} className='section preferences-panel'>
<div className='section-container'>
<div key='heading' className='section-heading'>
<i className='icon'>{definition.icon}</i>{definition.title}
</div>
{helpElem}
<div key='body' className='section-body'>
{controls}
</div>
</div>
</section>
)
}
function renderCheckbox (definition, value, callback) {
var iconClass = 'icon clickable'
if (value) iconClass += ' enabled'
return (
<div key='{definition.key}' className='control-group'>
<div className='controls'>
<label className='control-label'>
<div className='preference-title'>{definition.label}</div>
</label>
<div className='controls'>
<label className='clickable' onClick={handleClick}>
<i
className={iconClass}
id='{definition.property}'
>
check_circle
</i>
<span className='checkbox-label'>{definition.description}</span>
</label>
</div>
</div>
</div>
)
function handleClick () {
callback(!value)
}
}
// Creates a file chooser
// - defition should be {label, description, options}
// options are passed to dialog.showOpenDialog
// - value should be the current pref, a file or folder path
// - callback takes a new file or folder path
function renderFileSelector (definition, value, callback) {
var controls = [(
<input
type='text'
className='file-picker-text'
key={definition.property}
id={definition.property}
disabled='disabled'
value={value} />
), (
<button
key={definition.property + '-btn'}
className='btn'
onClick={handleClick}>
<i className='icon'>folder_open</i>
</button>
)]
return renderControlGroup(definition, controls)
function handleClick () {
dialog.showOpenDialog(remote.getCurrentWindow(), definition.options, function (filenames) {
if (!Array.isArray(filenames)) return
callback(filenames[0])
})
}
}
function renderControlGroup (definition, controls) {
return (
<div key={definition.key} className='control-group'>
<div className='controls'>
<label className='control-label'>
<div className='preference-title'>{definition.label}</div>
<div className='preference-description'>{definition.description}</div>
</label>
<div className='controls'>
{controls}
</div>
</div>
</div>
)
}

Some files were not shown because too many files have changed in this diff Show More