From 56e315f69ff29672f9226cbc4429dd1eb86c8c29 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 22 Oct 2023 21:13:55 -0600 Subject: [PATCH 01/19] Update ComposeModal component, fix multi filter bug and allow media re-ordering before upload/posting --- .../assets/js/components/ComposeModal.vue | 242 +++++++++++++----- 1 file changed, 173 insertions(+), 69 deletions(-) diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index 578679d32..745284859 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -178,9 +178,13 @@ Next - Post + Post - Post + Post + Next @@ -201,10 +205,10 @@ -
+
-
+
@@ -236,7 +240,7 @@
-
+
@@ -337,7 +341,7 @@
-
+
-
+
@@ -368,7 +372,9 @@ @@ -376,20 +382,21 @@
-
-
+
@@ -524,7 +531,7 @@
-
+
When you tag someone, they are sent a notification.
For more information on tagging, click here.

-
+

Tagging someone is like mentioning them, with the option to make it private between you.

You can choose to tag someone in public or private mode. Public mode will allow others to see who you tagged in the post and private mode tagged users will not be shown to others.

-
+

Add Location

-
+
@@ -910,6 +923,7 @@ export default { }, namedPages: [ + 'filteringMedia', 'cropPhoto', 'tagPeople', 'addLocation', @@ -943,7 +957,6 @@ export default { cb(res.data); }) .catch(err => { - console.log(err); }) }) }, @@ -957,7 +970,6 @@ export default { cb(res.data); }) .catch(err => { - console.log(err); }) }) } @@ -1032,6 +1044,10 @@ export default { collectionsPage: 1, collectionsCanLoadMore: false, spoilerText: undefined, + isFilteringMedia: false, + filteringMediaTimeout: undefined, + filteringRemainingCount: 0, + isPosting: false, } }, @@ -1242,6 +1258,50 @@ export default { }); }, + mediaReorder(dir) { + const m = this.media; + const cur = this.carouselCursor; + const pla = m[cur]; + let res = []; + let cursor = 0; + + if(dir == 'prev') { + if(cur == 0) { + for (let i = cursor; i < m.length - 1; i++) { + res[i] = m[i+1]; + } + res[m.length - 1] = pla; + cursor = 0; + } else { + res = this.handleSwap(m, cur, cur - 1); + cursor = cur - 1; + } + } else { + if(cur == m.length - 1) { + res = m; + let lastItem = res.pop(); + res.unshift(lastItem); + cursor = m.length - 1; + } else { + res = this.handleSwap(m, cur, cur + 1); + cursor = cur + 1; + } + } + this.$nextTick(() => { + this.media = res; + this.carouselCursor = cursor; + }) + }, + + handleSwap(arr, index1, index2) { + if (index1 >= 0 && index1 < arr.length && index2 >= 0 && index2 < arr.length) { + const temp = arr[index1]; + arr[index1] = arr[index2]; + arr[index2] = temp; + return arr; + } + }, + compose() { let state = this.composeState; @@ -1254,8 +1314,15 @@ export default { return; } + switch(state) { - case 'publish' : + case 'publish': + this.isPosting = true; + let count = this.media.filter(m => m.filter_class && !m.hasOwnProperty('is_filtered')).length; + if(count) { + this.applyFilterToMedia(); + return; + } if(this.composeSettings.media_descriptions === true) { let count = this.media.filter(m => { return !m.hasOwnProperty('alt') || m.alt.length < 2; @@ -1377,6 +1444,10 @@ export default { switch(this.mode) { case 'photo': switch(this.page) { + case 'filteringMedia': + this.page = 2; + break; + case 'addText': this.page = 1; break; @@ -1411,6 +1482,10 @@ export default { case 'video': switch(this.page) { + case 'filteringMedia': + this.page = 2; + break; + case 'licensePicker': this.page = 'video-2'; break; @@ -1431,6 +1506,10 @@ export default { this.page = 1; break; + case 'filteringMedia': + this.page = 2; + break; + case 'textOptions': this.page = 'addText'; break; @@ -1470,6 +1549,9 @@ export default { this.page = 2; break; + case 'filteringMedia': + break; + case 'cropPhoto': this.pageLoading = true; let self = this; @@ -1495,14 +1577,7 @@ export default { break; case 2: - if(this.currentFilter) { - if(window.confirm('Are you sure you want to apply this filter?')) { - this.applyFilterToMedia(); - this.page++; - } - } else { this.page++; - } break; case 3: this.page++; @@ -1649,43 +1724,73 @@ export default { // this is where the magic happens var ua = navigator.userAgent.toLowerCase(); if(ua.indexOf('firefox') == -1 && ua.indexOf('chrome') == -1) { + this.isPosting = false; swal('Oops!', 'Your browser does not support the filter feature.', 'error'); + this.page = 3; return; } - let medias = this.media; - let media = null; - const canvas = document.getElementById('pr_canvas'); - const ctx = canvas.getContext('2d'); - let image = document.getElementById('pr_img'); - let blob = null; - let data = null; - - for (var i = medias.length - 1; i >= 0; i--) { - media = medias[i]; - if(media.filter_class) { - image.src = media.url; - image.addEventListener('load', e => { - canvas.width = image.width; - canvas.height = image.height; - ctx.filter = App.util.filterCss[media.filter_class]; - ctx.drawImage(image, 0, 0, image.width, image.height); - ctx.save(); - canvas.toBlob(function(blob) { - data = new FormData(); - data.append('file', blob); - data.append('id', media.id); - axios.post('/api/compose/v0/media/update', data).then(res => { - }).catch(err => { - }); - }); - }, media.mime, 0.9); - ctx.clearRect(0, 0, image.width, image.height); - } - } - + let count = this.media.filter(m => m.filter_class).length; + if(count) { + this.page = 'filteringMedia'; + this.filteringRemainingCount = count; + this.$nextTick(() => { + this.isFilteringMedia = true; + this.media.forEach((media, idx) => this.applyFilterToMediaSave(media, idx)); + }) + } else { + this.page = 3; + } }, + applyFilterToMediaSave(media, idx) { + if(!media.filter_class) { + return; + } + + let self = this; + let data = null; + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + let image = document.createElement('img'); + image.src = media.url; + image.addEventListener('load', e => { + canvas.width = image.width; + canvas.height = image.height; + ctx.filter = App.util.filterCss[media.filter_class]; + ctx.drawImage(image, 0, 0, image.width, image.height); + ctx.save(); + canvas.toBlob(function(blob) { + data = new FormData(); + data.append('file', blob); + data.append('id', media.id); + axios.post('/api/compose/v0/media/update', data) + .then(res => { + self.media[idx].is_filtered = true; + self.updateFilteringMedia(); + }).catch(err => { + }); + }); + }, media.mime, 0.9); + ctx.clearRect(0, 0, image.width, image.height); + }, + + updateFilteringMedia() { + this.filteringRemainingCount--; + this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 500); + }, + + filteringMediaTimeoutJob() { + if(this.filteringRemainingCount === 0) { + this.isFilteringMedia = false; + clearTimeout(this.filteringMediaTimeout); + setTimeout(() => this.compose(), 500); + } else { + clearTimeout(this.filteringMediaTimeout); + this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 1000); + } + }, + tagSearch(input) { if (input.length < 1) { return []; } let self = this; @@ -1800,7 +1905,6 @@ export default { } window.location.href = res.data.url; }).catch(err => { - console.log(err.response.data.error); if(err.response.data.hasOwnProperty('error')) { if(err.response.data.error == 'Duplicate detected.') { this.postingPoll = false; From 00823545a5b9d3630989fad9c6a322b390e57068 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 22 Oct 2023 23:21:50 -0600 Subject: [PATCH 02/19] Add WebP2P support for Video --- config/media.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/config/media.php b/config/media.php index f550ff291..46c1719db 100644 --- a/config/media.php +++ b/config/media.php @@ -22,5 +22,39 @@ return [ 'resilient_mode' => env('ALT_PRI_ENABLED', false) || env('ALT_SEC_ENABLED', false), ], + ], + + 'hls' => [ + /* + |-------------------------------------------------------------------------- + | Enable HLS + |-------------------------------------------------------------------------- + | + | Enable optional HLS support, required for video p2p support. Requires FFMPEG + | Disabled by default. + | + */ + 'enabled' => env('MEDIA_HLS_ENABLED', false), + + 'debug' => env('MEDIA_HLS_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Enable Video P2P support + |-------------------------------------------------------------------------- + | + | Enable optional video p2p support. Requires FFMPEG + HLS + | Disabled by default. + | + */ + 'p2p' => env('MEDIA_HLS_P2P', false), + + 'p2p_debug' => env('MEDIA_HLS_P2P_DEBUG', false), + + 'bitrate' => env('MEDIA_HLS_BITRATE', 1000), + + 'tracker' => env('MEDIA_HLS_P2P_TRACKER', 'wss://tracker.webtorrent.dev'), + + 'ice' => env('MEDIA_HLS_P2P_ICE_SERVER', 'stun:stun.l.google.com:19302'), ] ]; From 82fc36b2b377e45fe26439e5f5a72a895c46a21c Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 22 Oct 2023 23:41:03 -0600 Subject: [PATCH 03/19] Update npm deps, add webp2p libs. Thanks @peertube <3 --- package-lock.json | 533 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +- 2 files changed, 533 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1dde4df84..ad3df5977 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,12 @@ "name": "pixelfed", "dependencies": { "@fancyapps/fancybox": "^3.5.7", + "@hcaptcha/vue-hcaptcha": "^1.3.0", + "@peertube/p2p-media-loader-core": "^1.0.14", + "@peertube/p2p-media-loader-hlsjs": "^1.0.14", "@trevoreyre/autocomplete-vue": "^2.2.0", "@web3-storage/parse-link-header": "^3.1.0", - "@zip.js/zip.js": "^2.7.14", + "@zip.js/zip.js": "^2.7.24", "animate.css": "^4.1.0", "bigpicture": "^2.6.2", "blurhash": "^1.1.3", @@ -1807,6 +1810,14 @@ "jquery": ">=1.9.0" } }, + "node_modules/@hcaptcha/vue-hcaptcha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hcaptcha/vue-hcaptcha/-/vue-hcaptcha-1.3.0.tgz", + "integrity": "sha512-aUSWyhRucgFeBOBUC3nWBZuE0TkeoSH5QIVFwiTLnNsYpIaxD1tKBbI5Tdoy0TdpkuXKsB4KqyElbvoMZ9reGw==", + "peerDependencies": { + "vue": "^2.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1918,6 +1929,29 @@ "npm": ">=5.0.0" } }, + "node_modules/@peertube/p2p-media-loader-core": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@peertube/p2p-media-loader-core/-/p2p-media-loader-core-1.0.14.tgz", + "integrity": "sha512-tjQv1CNziNY+zYzcL1h4q6AA2WuBUZnBIeVyjWR/EsO1EEC1VMdvPsL02cqYLz9yvIxgycjeTsWCm6XDqNgXRw==", + "dependencies": { + "bittorrent-tracker": "^9.19.0", + "debug": "^4.3.4", + "events": "^3.3.0", + "sha.js": "^2.4.11", + "simple-peer": "^9.11.1" + } + }, + "node_modules/@peertube/p2p-media-loader-hlsjs": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@peertube/p2p-media-loader-hlsjs/-/p2p-media-loader-hlsjs-1.0.14.tgz", + "integrity": "sha512-ySUVgUvAFXCE5E94xxjfywQ8xzk3jy9UGVkgi5Oqq+QeY7uG+o7CZ+LsQ/RjXgWBD70tEnyyfADHtL+9FCnwyQ==", + "dependencies": { + "@peertube/p2p-media-loader-core": "^1.0.14", + "debug": "^4.3.4", + "events": "^3.3.0", + "m3u8-parser": "^4.7.1" + } + }, "node_modules/@trevoreyre/autocomplete-vue": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@trevoreyre/autocomplete-vue/-/autocomplete-vue-2.4.1.tgz", @@ -2216,6 +2250,20 @@ "@types/node": "*" } }, + "node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, "node_modules/@vue/compiler-sfc": { "version": "2.7.14", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz", @@ -2464,10 +2512,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/@zip.js/zip.js": { - "version": "2.7.15", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.15.tgz", - "integrity": "sha512-iuL2otty04U4YBfdpd22XJacKaNuR7AHWlQrE/L9zHxfunXNtIeSgCxi66T74pnzHdmmpGqlBlZoVRkWOT70aA==", + "version": "2.7.30", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.30.tgz", + "integrity": "sha512-nhMvQCj+TF1ATBqYzFds7v+yxPBhdDYHh8J341KtC1D2UrVBUIYcYK4Jy1/GiTsxOXEiKOXSUxvPG/XR+7jMqw==", "engines": { + "bun": ">=0.7.0", "deno": ">=1.0.0", "node": ">=16.5.0" } @@ -2503,6 +2552,11 @@ "acorn": "^8" } }, + "node_modules/addr-to-ip-port": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.4.tgz", + "integrity": "sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==" + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -2826,6 +2880,11 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" }, + "node_modules/bencode": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", + "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2847,6 +2906,99 @@ "node": ">=8" } }, + "node_modules/bittorrent-peerid": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.6.tgz", + "integrity": "sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bittorrent-tracker": { + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.19.0.tgz", + "integrity": "sha512-09d0aD2b+MC+zWvWajkUAKkYMynYW4tMbTKiRSthKtJZbafzEoNQSUHyND24SoCe3ZOb2fKfa6fu2INAESL9wA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.1", + "bittorrent-peerid": "^1.3.3", + "bn.js": "^5.2.0", + "chrome-dgram": "^3.0.6", + "clone": "^2.0.0", + "compact2string": "^1.4.1", + "debug": "^4.1.1", + "ip": "^1.1.5", + "lru": "^3.1.0", + "minimist": "^1.2.5", + "once": "^1.4.0", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "randombytes": "^2.1.0", + "run-parallel": "^1.2.0", + "run-series": "^1.1.9", + "simple-get": "^4.0.0", + "simple-peer": "^9.11.0", + "simple-websocket": "^9.1.0", + "socks": "^2.0.0", + "string2compact": "^1.3.0", + "unordered-array-remove": "^1.0.2", + "ws": "^7.4.5" + }, + "bin": { + "bittorrent-tracker": "bin/cmd.js" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "bufferutil": "^4.0.3", + "utf-8-validate": "^5.0.5" + } + }, + "node_modules/bittorrent-tracker/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -3125,6 +3277,19 @@ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -3285,6 +3450,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/chrome-dgram": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", + "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "inherits": "^2.0.4", + "run-series": "^1.1.9" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -3349,6 +3537,14 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -3406,6 +3602,14 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "node_modules/compact2string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", + "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", + "dependencies": { + "ipaddr.js": ">= 0.1.5" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -3954,6 +4158,20 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -4266,6 +4484,11 @@ "node": ">=4" } }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4813,6 +5036,11 @@ "node": ">=6.9.0" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5422,6 +5650,11 @@ "node": ">= 0.10" } }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, "node_modules/ipaddr.js": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", @@ -5964,6 +6197,17 @@ "tslib": "^2.0.3" } }, + "node_modules/lru": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", + "integrity": "sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ==", + "dependencies": { + "inherits": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5972,6 +6216,16 @@ "yallist": "^3.0.2" } }, + "node_modules/m3u8-parser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz", + "integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6140,6 +6394,17 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -6311,6 +6576,17 @@ "node": ">= 6.13.0" } }, + "node_modules/node-gyp-build": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", + "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -7507,6 +7783,11 @@ } ] }, + "node_modules/random-iterate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", + "integrity": "sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7844,6 +8125,25 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8183,6 +8483,172 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/simple-peer/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/simple-peer/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + } + }, + "node_modules/simple-websocket/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/simple-websocket/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8191,6 +8657,15 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -8201,6 +8676,24 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -8333,6 +8826,15 @@ "node": ">=8" } }, + "node_modules/string2compact": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.2.tgz", + "integrity": "sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw==", + "dependencies": { + "addr-to-ip-port": "^1.0.1", + "ipaddr.js": "^2.0.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8677,6 +9179,11 @@ "node": ">= 10.0.0" } }, + "node_modules/unordered-array-remove": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", + "integrity": "sha512-45YsfD6svkgaCBNyvD+dFHm4qFX9g3wRSIVgWVPtm2OCnphvPxzJoe20ATsiNpNJrmzHifnxm+BN5F7gFT/4gw==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8744,6 +9251,24 @@ "resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.12.tgz", "integrity": "sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==" }, + "node_modules/url-toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", + "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==" + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", diff --git a/package.json b/package.json index 9a188a1d4..6598175d9 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,12 @@ }, "dependencies": { "@fancyapps/fancybox": "^3.5.7", + "@hcaptcha/vue-hcaptcha": "^1.3.0", + "@peertube/p2p-media-loader-core": "^1.0.14", + "@peertube/p2p-media-loader-hlsjs": "^1.0.14", "@trevoreyre/autocomplete-vue": "^2.2.0", "@web3-storage/parse-link-header": "^3.1.0", - "@zip.js/zip.js": "^2.7.14", + "@zip.js/zip.js": "^2.7.24", "animate.css": "^4.1.0", "bigpicture": "^2.6.2", "blurhash": "^1.1.3", From 4cd53247a66215ad7696b9bcab8571abccbe28c0 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 22 Oct 2023 23:42:25 -0600 Subject: [PATCH 04/19] Add MediaHlsService --- app/Services/Media/MediaHlsService.php | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 app/Services/Media/MediaHlsService.php diff --git a/app/Services/Media/MediaHlsService.php b/app/Services/Media/MediaHlsService.php new file mode 100644 index 000000000..04b5ac649 --- /dev/null +++ b/app/Services/Media/MediaHlsService.php @@ -0,0 +1,27 @@ +media_path; + if(!$path) { return; } + $parts = explode('/', $path); + $filename = array_pop($parts); + $dir = implode('/', $parts); + [$name, $ext] = explode('.', $filename); + + $files = Storage::files($dir); + + return collect($files) + ->filter(function($p) use($dir, $name) { + return str_starts_with($p, $dir . '/' . $name); + }) + ->values() + ->toArray(); + } +} From a144301085b83769132bb634161d7a97be4509f9 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 00:11:46 -0600 Subject: [PATCH 05/19] Add RegisterForm component --- .../remote-auth/partials/RegisterForm.vue | 800 ++++++++++++++++++ 1 file changed, 800 insertions(+) create mode 100644 resources/assets/components/remote-auth/partials/RegisterForm.vue diff --git a/resources/assets/components/remote-auth/partials/RegisterForm.vue b/resources/assets/components/remote-auth/partials/RegisterForm.vue new file mode 100644 index 000000000..6b1c4047f --- /dev/null +++ b/resources/assets/components/remote-auth/partials/RegisterForm.vue @@ -0,0 +1,800 @@ + + + + + From 4e3e23db36fe28f34c36d2d55272e86d1598b442 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 00:15:53 -0600 Subject: [PATCH 06/19] Add js debounce util --- resources/assets/js/util/debounce.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 resources/assets/js/util/debounce.js diff --git a/resources/assets/js/util/debounce.js b/resources/assets/js/util/debounce.js new file mode 100644 index 000000000..b846d35e5 --- /dev/null +++ b/resources/assets/js/util/debounce.js @@ -0,0 +1,11 @@ +export function debounce (fn, delay) { + var timeoutID = null + return function () { + clearTimeout(timeoutID) + var args = arguments + var that = this + timeoutID = setTimeout(function () { + fn.apply(that, args) + }, delay) + } +} From fac7c3c5e79f4572d549d3767e02a577451dffad Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 00:38:37 -0600 Subject: [PATCH 07/19] Update MediaTransformer, add hls_manifest attribute --- app/Transformer/Api/MediaTransformer.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Transformer/Api/MediaTransformer.php b/app/Transformer/Api/MediaTransformer.php index 0a4600bae..e1eeb1f6f 100644 --- a/app/Transformer/Api/MediaTransformer.php +++ b/app/Transformer/Api/MediaTransformer.php @@ -4,6 +4,7 @@ namespace App\Transformer\Api; use App\Media; use League\Fractal; +use Storage; class MediaTransformer extends Fractal\TransformerAbstract { @@ -28,6 +29,10 @@ class MediaTransformer extends Fractal\TransformerAbstract 'blurhash' => $media->blurhash ?? 'U4Rfzst8?bt7ogayj[j[~pfQ9Goe%Mj[WBay' ]; + if(config('media.hls.enabled') && $media->hls_transcoded_at != null && $media->hls_path) { + $res['hls_manifest'] = url(Storage::url($media->hls_path)); + } + if($media->width && $media->height) { $res['meta'] = [ 'focus' => [ From f9bbb055755c4a985e51209bb0ffd66e852be866 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 01:09:16 -0600 Subject: [PATCH 08/19] Update MediaDeletePipeline, handle HLS deletion --- .../MediaPipeline/MediaDeletePipeline.php | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/app/Jobs/MediaPipeline/MediaDeletePipeline.php b/app/Jobs/MediaPipeline/MediaDeletePipeline.php index 4db76c9c7..55df84948 100644 --- a/app/Jobs/MediaPipeline/MediaDeletePipeline.php +++ b/app/Jobs/MediaPipeline/MediaDeletePipeline.php @@ -10,8 +10,11 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Storage; +use App\Services\Media\MediaHlsService; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; -class MediaDeletePipeline implements ShouldQueue +class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -20,8 +23,34 @@ class MediaDeletePipeline implements ShouldQueue public $timeout = 300; public $tries = 3; public $maxExceptions = 1; + public $failOnTimeout = true; public $deleteWhenMissingModels = true; + /** + * The number of seconds after which the job's unique lock will be released. + * + * @var int + */ + public $uniqueFor = 3600; + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'media:purge-job:id-' . $this->media->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("media:purge-job:id-{$this->media->id}"))->shared()->dontRelease()]; + } + public function __construct(Media $media) { $this->media = $media; @@ -63,9 +92,17 @@ class MediaDeletePipeline implements ShouldQueue $disk->delete($thumb); } + if($media->hls_path != null) { + $files = MediaHlsService::allFiles($media); + if($files && count($files)) { + foreach($files as $file) { + $disk->delete($file); + } + } + } + $media->delete(); return 1; } - } From 3f292459ffaf5e851fa932010c2dcb3b117df10d Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 01:12:40 -0600 Subject: [PATCH 09/19] Update VideoPipeline, add VideoHlsPipeline job for HLS generation --- app/Jobs/VideoPipeline/VideoHlsPipeline.php | 94 +++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/Jobs/VideoPipeline/VideoHlsPipeline.php diff --git a/app/Jobs/VideoPipeline/VideoHlsPipeline.php b/app/Jobs/VideoPipeline/VideoHlsPipeline.php new file mode 100644 index 000000000..eb91ed78b --- /dev/null +++ b/app/Jobs/VideoPipeline/VideoHlsPipeline.php @@ -0,0 +1,94 @@ +media->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("media:video-hls:id-{$this->media->id}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($media) + { + $this->media = $media; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $media = $this->media; + + $bitrate = (new X264)->setKiloBitrate(config('media.hls.bitrate') ?? 1000); + + $mp4 = $media->media_path; + $man = str_replace('.mp4', '.m3u8', $mp4); + + FFMpeg::fromDisk('local') + ->open($mp4) + ->exportForHLS() + ->setSegmentLength(16) + ->setKeyFrameInterval(48) + ->addFormat($bitrate) + ->save($man); + + $media->hls_path = $man; + $media->hls_transcoded_at = now(); + $media->save(); + + MediaService::del($media->status_id); + usleep(50000); + StatusService::del($media->status_id); + + return; + } +} From f0ba2dfc69163b6e499ea1f2095c9a290d88db7d Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 01:13:09 -0600 Subject: [PATCH 10/19] Update VideoThumbnail job, dispatch HLS job when applicable --- app/Jobs/VideoPipeline/VideoThumbnail.php | 40 +++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/app/Jobs/VideoPipeline/VideoThumbnail.php b/app/Jobs/VideoPipeline/VideoThumbnail.php index fed61e4fc..ebcb4cf7e 100644 --- a/app/Jobs/VideoPipeline/VideoThumbnail.php +++ b/app/Jobs/VideoPipeline/VideoThumbnail.php @@ -16,13 +16,46 @@ use App\Jobs\MediaPipeline\MediaStoragePipeline; use App\Util\Media\Blurhash; use App\Services\MediaService; use App\Services\StatusService; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; -class VideoThumbnail implements ShouldQueue +class VideoThumbnail implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $media; + public $timeout = 900; + public $tries = 3; + public $maxExceptions = 1; + public $failOnTimeout = true; + public $deleteWhenMissingModels = true; + + /** + * The number of seconds after which the job's unique lock will be released. + * + * @var int + */ + public $uniqueFor = 3600; + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'media:video-thumb:id-' . $this->media->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("media:video-thumb:id-{$this->media->id}"))->shared()->dontRelease()]; + } + /** * Create a new job instance. * @@ -54,7 +87,7 @@ class VideoThumbnail implements ShouldQueue $path[$i] = $t; $save = implode('/', $path); $video = FFMpeg::open($base) - ->getFrameFromSeconds(0) + ->getFrameFromSeconds(1) ->export() ->toDisk('local') ->save($save); @@ -68,6 +101,9 @@ class VideoThumbnail implements ShouldQueue $media->save(); } + if(config('media.hls.enabled')) { + VideoHlsPipeline::dispatch($media)->onQueue('mmo'); + } } catch (Exception $e) { } From 6cf4363c507455782e03d300305c3d07e8405544 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 01:14:33 -0600 Subject: [PATCH 11/19] Update MediaService, remove hls_manifest attribute for MastoAPI entities --- app/Services/MediaService.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Services/MediaService.php b/app/Services/MediaService.php index 8ca90118f..e0a2576e0 100644 --- a/app/Services/MediaService.php +++ b/app/Services/MediaService.php @@ -18,7 +18,7 @@ class MediaService public static function get($statusId) { - return Cache::remember(self::CACHE_KEY.$statusId, 86400, function() use($statusId) { + return Cache::remember(self::CACHE_KEY.$statusId, 21600, function() use($statusId) { $media = Media::whereStatusId($statusId)->orderBy('order')->get(); if(!$media) { return []; @@ -46,7 +46,8 @@ class MediaService $media['orientation'], $media['filter_name'], $media['filter_class'], - $media['mime'] + $media['mime'], + $media['hls_manifest'] ); $media['type'] = $mime ? strtolower($mime[0]) : 'unknown'; From 5c358010b088e5f72a6218e148c4414963644cf9 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 01:15:02 -0600 Subject: [PATCH 12/19] Update Config util, add hls attributes --- app/Util/Site/Config.php | 156 +++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 71 deletions(-) diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php index 90631159f..e0916591d 100644 --- a/app/Util/Site/Config.php +++ b/app/Util/Site/Config.php @@ -7,86 +7,100 @@ use Illuminate\Support\Str; class Config { - const CACHE_KEY = 'api:site:configuration:_v0.8'; + const CACHE_KEY = 'api:site:configuration:_v0.8'; - public static function get() { - return Cache::remember(self::CACHE_KEY, 900, function() { - return [ - 'version' => config('pixelfed.version'), - 'open_registration' => (bool) config_cache('pixelfed.open_registration'), - 'uploader' => [ - 'max_photo_size' => (int) config('pixelfed.max_photo_size'), - 'max_caption_length' => (int) config('pixelfed.max_caption_length'), - 'max_altext_length' => (int) config('pixelfed.max_altext_length', 150), - 'album_limit' => (int) config_cache('pixelfed.max_album_length'), - 'image_quality' => (int) config_cache('pixelfed.image_quality'), + public static function get() { + return Cache::remember(self::CACHE_KEY, 900, function() { + $hls = [ + 'enabled' => config('media.hls.enabled'), + ]; + if(config('media.hls.enabled')) { + $hls = [ + 'enabled' => true, + 'debug' => (bool) config('media.hls.debug'), + 'p2p' => (bool) config('media.hls.p2p'), + 'p2p_debug' => (bool) config('media.hls.p2p_debug'), + 'tracker' => config('media.hls.tracker'), + 'ice' => config('media.hls.ice') + ]; + } + return [ + 'version' => config('pixelfed.version'), + 'open_registration' => (bool) config_cache('pixelfed.open_registration'), + 'uploader' => [ + 'max_photo_size' => (int) config('pixelfed.max_photo_size'), + 'max_caption_length' => (int) config('pixelfed.max_caption_length'), + 'max_altext_length' => (int) config('pixelfed.max_altext_length', 150), + 'album_limit' => (int) config_cache('pixelfed.max_album_length'), + 'image_quality' => (int) config_cache('pixelfed.image_quality'), - 'max_collection_length' => (int) config('pixelfed.max_collection_length', 18), + 'max_collection_length' => (int) config('pixelfed.max_collection_length', 18), - 'optimize_image' => (bool) config('pixelfed.optimize_image'), - 'optimize_video' => (bool) config('pixelfed.optimize_video'), + 'optimize_image' => (bool) config('pixelfed.optimize_image'), + 'optimize_video' => (bool) config('pixelfed.optimize_video'), - 'media_types' => config_cache('pixelfed.media_types'), - 'mime_types' => config_cache('pixelfed.media_types') ? explode(',', config_cache('pixelfed.media_types')) : [], - 'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit') - ], + 'media_types' => config_cache('pixelfed.media_types'), + 'mime_types' => config_cache('pixelfed.media_types') ? explode(',', config_cache('pixelfed.media_types')) : [], + 'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit') + ], - 'activitypub' => [ - 'enabled' => (bool) config_cache('federation.activitypub.enabled'), - 'remote_follow' => config('federation.activitypub.remoteFollow') - ], + 'activitypub' => [ + 'enabled' => (bool) config_cache('federation.activitypub.enabled'), + 'remote_follow' => config('federation.activitypub.remoteFollow') + ], - 'ab' => config('exp'), + 'ab' => config('exp'), - 'site' => [ - 'name' => config_cache('app.name'), - 'domain' => config('pixelfed.domain.app'), - 'url' => config('app.url'), - 'description' => config_cache('app.short_description') - ], + 'site' => [ + 'name' => config_cache('app.name'), + 'domain' => config('pixelfed.domain.app'), + 'url' => config('app.url'), + 'description' => config_cache('app.short_description') + ], - 'account' => [ - 'max_avatar_size' => config('pixelfed.max_avatar_size'), - 'max_bio_length' => config('pixelfed.max_bio_length'), - 'max_name_length' => config('pixelfed.max_name_length'), - 'min_password_length' => config('pixelfed.min_password_length'), - 'max_account_size' => config('pixelfed.max_account_size') - ], + 'account' => [ + 'max_avatar_size' => config('pixelfed.max_avatar_size'), + 'max_bio_length' => config('pixelfed.max_bio_length'), + 'max_name_length' => config('pixelfed.max_name_length'), + 'min_password_length' => config('pixelfed.min_password_length'), + 'max_account_size' => config('pixelfed.max_account_size') + ], - 'username' => [ - 'remote' => [ - 'formats' => config('instance.username.remote.formats'), - 'format' => config('instance.username.remote.format'), - 'custom' => config('instance.username.remote.custom') - ] - ], + 'username' => [ + 'remote' => [ + 'formats' => config('instance.username.remote.formats'), + 'format' => config('instance.username.remote.format'), + 'custom' => config('instance.username.remote.custom') + ] + ], - 'features' => [ - 'timelines' => [ - 'local' => true, - 'network' => (bool) config('federation.network_timeline'), - ], - 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'), - 'stories' => (bool) config_cache('instance.stories.enabled'), - 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'), - 'import' => [ - 'instagram' => (bool) config_cache('pixelfed.import.instagram.enabled'), - 'mastodon' => false, - 'pixelfed' => false - ], - 'label' => [ - 'covid' => [ - 'enabled' => (bool) config('instance.label.covid.enabled'), - 'org' => config('instance.label.covid.org'), - 'url' => config('instance.label.covid.url'), - ] - ] - ] - ]; - }); - } + 'features' => [ + 'timelines' => [ + 'local' => true, + 'network' => (bool) config('federation.network_timeline'), + ], + 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'), + 'stories' => (bool) config_cache('instance.stories.enabled'), + 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'), + 'import' => [ + 'instagram' => (bool) config_cache('pixelfed.import.instagram.enabled'), + 'mastodon' => false, + 'pixelfed' => false + ], + 'label' => [ + 'covid' => [ + 'enabled' => (bool) config('instance.label.covid.enabled'), + 'org' => config('instance.label.covid.org'), + 'url' => config('instance.label.covid.url'), + ] + ], + 'hls' => $hls + ] + ]; + }); + } - public static function json() { - return json_encode(self::get(), JSON_FORCE_OBJECT); - } + public static function json() { + return json_encode(self::get(), JSON_FORCE_OBJECT); + } } From e3f8cfb49e1d8f2ead142de284e248085793bfe7 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 01:27:11 -0600 Subject: [PATCH 13/19] Add hls/p2p video player --- .../components/presenter/VideoPlayer.vue | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 resources/assets/components/presenter/VideoPlayer.vue diff --git a/resources/assets/components/presenter/VideoPlayer.vue b/resources/assets/components/presenter/VideoPlayer.vue new file mode 100644 index 000000000..8f5ba0102 --- /dev/null +++ b/resources/assets/components/presenter/VideoPlayer.vue @@ -0,0 +1,198 @@ + + + From f11ce7009ff92d97b9868aaefa99702cc3b1d5ca Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 23 Oct 2023 01:28:06 -0600 Subject: [PATCH 14/19] Update PostContent, add new video-player component --- .../components/partials/post/PostContent.vue | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/resources/assets/components/partials/post/PostContent.vue b/resources/assets/components/partials/post/PostContent.vue index 057b07fea..0a88acb19 100644 --- a/resources/assets/components/partials/post/PostContent.vue +++ b/resources/assets/components/partials/post/PostContent.vue @@ -12,7 +12,7 @@
- +
@@ -108,27 +108,11 @@
- +
@@ -185,12 +169,14 @@