hls: initial commit
parent
32966d75af
commit
8a16deeec4
|
@ -57,6 +57,8 @@ return [
|
|||
|
||||
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
||||
|
||||
['name' => 'Video#transcode', 'url' => '/api/video/transcode/{fileid}/{profile}', 'verb' => 'GET'],
|
||||
|
||||
// Config API
|
||||
['name' => 'Other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
|
||||
|
||||
|
|
|
@ -95,6 +95,11 @@ class PageController extends Controller
|
|||
$policy->addAllowedWorkerSrcDomain("'self'");
|
||||
$policy->addAllowedScriptDomain("'self'");
|
||||
|
||||
// Video player
|
||||
$policy->addAllowedWorkerSrcDomain('blob:');
|
||||
$policy->addAllowedScriptDomain('blob:');
|
||||
$policy->addAllowedMediaDomain('blob:');
|
||||
|
||||
// Allow nominatim for metadata
|
||||
$policy->addAllowedConnectDomain('nominatim.openstreetmap.org');
|
||||
$policy->addAllowedFrameDomain('www.openstreetmap.org');
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 Varun Patil <radialapps@gmail.com>
|
||||
* @author Varun Patil <radialapps@gmail.com>
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCA\Memories\Controller;
|
||||
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||
|
||||
class VideoController extends ApiBase
|
||||
{
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* Transcode a video to HLS by proxy
|
||||
*
|
||||
* @param string fileid
|
||||
* @param string video profile
|
||||
*
|
||||
* @return JSONResponse an empty JSONResponse with respective http status code
|
||||
*/
|
||||
public function transcode(string $fileid, string $profile): Http\Response
|
||||
{
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
// Make sure not running in read-only mode
|
||||
if ($this->config->getSystemValue('memories.no_transcode', false)) {
|
||||
return new JSONResponse(['message' => 'Transcoding disabled'], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
// Get file
|
||||
$files = $this->rootFolder->getById($fileid);
|
||||
if (count($files) === 0) {
|
||||
return new JSONResponse(['message' => 'File not found'], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
$file = $files[0];
|
||||
|
||||
// Local files only for now
|
||||
if (!$file->getStorage()->isLocal()) {
|
||||
return new JSONResponse(['message' => 'External storage not supported'], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
// Get file path
|
||||
$path = $file->getStorage()->getLocalFile($file->getInternalPath());
|
||||
if (!$path || !file_exists($path)) {
|
||||
return new JSONResponse(['message' => 'File not found'], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Make upstream request
|
||||
$ch = curl_init("http://localhost:9999/vod/$path/$profile");
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
$data = curl_exec($ch);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
$returnCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// Check data was received
|
||||
if ($returnCode >= 400 || false === $data) {
|
||||
return new JSONResponse(['message' => 'Transcode failed'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// Create and send response
|
||||
$response = new DataDisplayResponse($data, Http::STATUS_OK, [
|
||||
'Content-Type' => $contentType,
|
||||
]);
|
||||
$response->cacheFor(3600 * 24, false, false);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
"@nextcloud/paths": "^2.1.0",
|
||||
"@nextcloud/sharing": "^0.1.0",
|
||||
"@nextcloud/vue": "7.0.0",
|
||||
"@silvermine/videojs-quality-selector": "^1.2.5",
|
||||
"camelcase": "^7.0.0",
|
||||
"filerobot-image-editor": "^4.3.7",
|
||||
"justified-layout": "^4.1.0",
|
||||
|
@ -2162,6 +2163,18 @@
|
|||
"styled-components": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@silvermine/videojs-quality-selector": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@silvermine/videojs-quality-selector/-/videojs-quality-selector-1.2.5.tgz",
|
||||
"integrity": "sha512-cielchUzL8r2EX01S7PfR54tTbxDZR53xIDJoUi9Wg6pM2X+ftdJD6XiIDVOjPlBoG94iuG9LJwUtjX5IhrWZQ==",
|
||||
"dependencies": {
|
||||
"class.extend": "0.9.1",
|
||||
"underscore": "1.13.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"video.js": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
|
@ -3539,6 +3552,11 @@
|
|||
"resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
|
||||
"integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA=="
|
||||
},
|
||||
"node_modules/class.extend": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/class.extend/-/class.extend-0.9.1.tgz",
|
||||
"integrity": "sha512-Tzj+2kAkZs+iGiUOUoKvtj4c/SjeVdKZXg/NbLTGKu0kp66h69dyMHQwOSzuyIghXAUswuY24TZc0HdaJCXx2A=="
|
||||
},
|
||||
"node_modules/clone-deep": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
|
||||
|
@ -9072,6 +9090,11 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",
|
||||
"integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g=="
|
||||
},
|
||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
|
||||
|
@ -11900,6 +11923,15 @@
|
|||
"use-callback-ref": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"@silvermine/videojs-quality-selector": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@silvermine/videojs-quality-selector/-/videojs-quality-selector-1.2.5.tgz",
|
||||
"integrity": "sha512-cielchUzL8r2EX01S7PfR54tTbxDZR53xIDJoUi9Wg6pM2X+ftdJD6XiIDVOjPlBoG94iuG9LJwUtjX5IhrWZQ==",
|
||||
"requires": {
|
||||
"class.extend": "0.9.1",
|
||||
"underscore": "1.13.1"
|
||||
}
|
||||
},
|
||||
"@surma/rollup-plugin-off-main-thread": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
|
@ -13056,6 +13088,11 @@
|
|||
"resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
|
||||
"integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA=="
|
||||
},
|
||||
"class.extend": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/class.extend/-/class.extend-0.9.1.tgz",
|
||||
"integrity": "sha512-Tzj+2kAkZs+iGiUOUoKvtj4c/SjeVdKZXg/NbLTGKu0kp66h69dyMHQwOSzuyIghXAUswuY24TZc0HdaJCXx2A=="
|
||||
},
|
||||
"clone-deep": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
|
||||
|
@ -17325,6 +17362,11 @@
|
|||
"which-boxed-primitive": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",
|
||||
"integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g=="
|
||||
},
|
||||
"unicode-canonical-property-names-ecmascript": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"@nextcloud/paths": "^2.1.0",
|
||||
"@nextcloud/sharing": "^0.1.0",
|
||||
"@nextcloud/vue": "7.0.0",
|
||||
"@silvermine/videojs-quality-selector": "^1.2.5",
|
||||
"camelcase": "^7.0.0",
|
||||
"filerobot-image-editor": "^4.3.7",
|
||||
"justified-layout": "^4.1.0",
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import PhotoSwipe from "photoswipe";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
|
||||
import videojs from "video.js";
|
||||
import "video.js/dist/video-js.min.css";
|
||||
import qualitySelector from "@silvermine/videojs-quality-selector";
|
||||
import "@silvermine/videojs-quality-selector/dist/css/quality-selector.css";
|
||||
qualitySelector(videojs);
|
||||
|
||||
/**
|
||||
* Check if slide has video content
|
||||
|
@ -71,19 +76,53 @@ class VideoContentSetup {
|
|||
e.preventDefault();
|
||||
} else {
|
||||
const content = <any>e.slide.content;
|
||||
const fileid = content.data.photo.fileid;
|
||||
|
||||
// Create hls sources if enabled
|
||||
let hlsSources = [];
|
||||
const baseUrl = generateUrl(
|
||||
`/apps/memories/api/video/transcode/${fileid}`
|
||||
);
|
||||
for (const q of ["360p", "480p", "720p", "1080p"]) {
|
||||
hlsSources.push({
|
||||
src: `${baseUrl}/${q}.m3u8`,
|
||||
label: q,
|
||||
type: "application/x-mpegURL",
|
||||
selected: q === "480p" ? true : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
content.videojs = videojs(content.videoElement, {
|
||||
fluid: true,
|
||||
autoplay: true,
|
||||
controls: true,
|
||||
sources: [{ src: e.slide.data.src }],
|
||||
sources: [
|
||||
...hlsSources,
|
||||
{
|
||||
src: e.slide.data.src,
|
||||
label: "Original",
|
||||
},
|
||||
],
|
||||
preload: "metadata",
|
||||
inactivityTimeout: 0,
|
||||
html5: {
|
||||
vhs: {
|
||||
withCredentials: true,
|
||||
overrideNative: !videojs.browser.IS_SAFARI,
|
||||
withCredentials: false,
|
||||
},
|
||||
},
|
||||
controlBar: {
|
||||
children: [
|
||||
"playToggle",
|
||||
"progressControl",
|
||||
"volumePanel",
|
||||
"qualitySelector",
|
||||
"fullscreenToggle",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
globalThis.videojs = content.videojs;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue