Skip to content

Commit

Permalink
Fix embedded player on publicly shared folders on NC31
Browse files Browse the repository at this point in the history
In Nextcloud 31, also the public link-shared folders use the new Vue UI
instead of the legacy Files UI. Hence, the "NC28+" logic in the embedded Music
player now needs to be able to handle also the public shares.

To easily stream the file data from public shares, both when playing an audio
file and when playing a playlist, a new endpoint
`/api/share/{token}/{fileId}/download` was added. The same endpoints provided
by the Music app back-end are now used regardless of the host cloud version.

refs owncloud#1198
  • Loading branch information
paulijar committed Jan 30, 2025
1 parent 434539c commit a69bb24
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 33 deletions.
3 changes: 2 additions & 1 deletion appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @author Morris Jobke <hey@morrisjobke.de>
* @author Pauli Järvinen <pauli.jarvinen@gmail.com>
* @copyright Morris Jobke 2014
* @copyright Pauli Järvinen 2017 - 2024
* @copyright Pauli Järvinen 2017 - 2025
*/

namespace OCA\Music;
Expand Down Expand Up @@ -65,6 +65,7 @@
//['name' => 'shivaApi#trackLyrics','url' => '/api/track/{trackId}/lyrics', 'verb' => 'GET'],

['name' => 'share#fileInfo', 'url' => '/api/share/{token}/{fileId}/info', 'verb' => 'GET'],
['name' => 'share#download', 'url' => '/api/share/{token}/{fileId}/download','verb' => 'GET'],
['name' => 'share#parsePlaylist', 'url' => '/api/share/{token}/{fileId}/parse', 'verb' => 'GET'],

// playlist API
Expand Down
6 changes: 3 additions & 3 deletions js/embedded/fileshareview.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* later. See the COPYING file.
*
* @author Pauli Järvinen <pauli.jarvinen@gmail.com>
* @copyright Pauli Järvinen 2017 - 2024
* @copyright Pauli Järvinen 2017 - 2025
*/

import playOverlayPath from '../../img/play-overlay.svg';
Expand All @@ -20,7 +20,7 @@ OCA.Music.FileShareView = class {
#mPlayer;
#mShareToken;

constructor(embeddedPlayer, supportedMimes) {
constructor(embeddedPlayer, supportedMimes, sharingToken) {
// Add click handler to the file preview if this is a supported file.
// The feature is disabled on old IE versions where there's no MutationObserver and
// $.initialize would not work.
Expand All @@ -31,7 +31,7 @@ OCA.Music.FileShareView = class {
&& supportedMimes.includes($('#mimetype').val()))
{
this.#mPlayer = embeddedPlayer;
this.#mShareToken = $('#sharingToken').val();
this.#mShareToken = sharingToken;
this.#initView();
}
}
Expand Down
28 changes: 15 additions & 13 deletions js/embedded/folderview.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* later. See the COPYING file.
*
* @author Pauli Järvinen <pauli.jarvinen@gmail.com>
* @copyright Pauli Järvinen 2017 - 2024
* @copyright Pauli Järvinen 2017 - 2025
*/

import playIconPath from '../../img/play-big.svg';
Expand All @@ -21,7 +21,7 @@ OCA.Music.FolderView = class {
#currentFile = null; // may be an audio file or a playlist file
#playingListFile = false;
#fileList = null; // FileList from Files (prior to NC28) or Sharing app
#shareToken = $('#sharingToken').val(); // undefined when not on share page
#shareToken = null;
#audioMimes = null;
#playlistMimes = null;

Expand All @@ -43,15 +43,19 @@ OCA.Music.FolderView = class {
);
}

registerToNcFiles(ncFiles) {
registerToNcFiles(ncFiles, sharingToken) {
this.#shareToken = sharingToken;

const audioFileCb = this.#createFileClickCallback(() => this.#openAudioFile());
const plFileCb = this.#createFileClickCallback(() => this.#openPlaylistFile());

this.#registerToNcFiles(ncFiles, this.#audioMimes, audioFileCb, 'music_play_audio_file');
this.#registerToNcFiles(ncFiles, this.#playlistMimes, plFileCb, 'music_play_playlist_file');
}

registerToFileActions(fileActions) {
registerToFileActions(fileActions, sharingToken) {
this.#shareToken = sharingToken;

const audioFileCb = this.#createFileClickCallback(() => this.#openAudioFile());
const plFileCb = this.#createFileClickCallback(() => this.#openPlaylistFile());

Expand Down Expand Up @@ -92,17 +96,15 @@ OCA.Music.FolderView = class {
}

#urlForFile(file) {
let url = this.#fileList
? this.#fileList.getDownloadUrl(file.name, file.path)
: OC.filePath('music', '', 'index.php') + '/api/file/' + file.id + '/download';
// Use download endpoints provided by the Music back-end instead of relying on mFileList.getDownloadUrl or the
// file.source from ncFiles.FileAction (on NC28+). This has the benefit of working the same way on all platform
// versions and when playing individual audio files or a playlist.

// Append request token unless this is a public share. This is actually unnecessary for most files
// When not playing a public share, we add the request token. This is actually unnecessary for most files
// but needed when the file in question is played using our Aurora.js fallback player.
if (!this.#shareToken) {
let delimiter = _.includes(url, '?') ? '&' : '?';
url += delimiter + 'requesttoken=' + encodeURIComponent(OC.requestToken);
}
return url;
return this.#shareToken
? OC.generateUrl(`apps/music/api/share/${this.#shareToken}/${file.id}/download`)
: OC.generateUrl(`apps/music/api/file/${file.id}/download`) + '?requesttoken=' + encodeURIComponent(OC.requestToken);
}

#onClose() {
Expand Down
17 changes: 12 additions & 5 deletions js/embedded/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* later. See the COPYING file.
*
* @author Pauli Järvinen <pauli.jarvinen@gmail.com>
* @copyright Pauli Järvinen 2017 - 2024
* @copyright Pauli Järvinen 2017 - 2025
*/

(function() {
Expand Down Expand Up @@ -37,19 +37,26 @@
// the call succeeds on ownCloud but the registration just does nothing there. Note that we can't wait
// for the page load to be finished before doing this because that would be too late for the registration
// and cause the issue https://github.com/owncloud/music/issues/1126.
import('@nextcloud/files').then(ncFiles => OCA.Music.folderView.registerToNcFiles(ncFiles)).catch(_e => {/*ignore*/});
import('@nextcloud/files').then(ncFiles => {
import('@nextcloud/sharing/public').then(ncSharingPublic => {
const sharingToken = ncSharingPublic.isPublicShare() ? ncSharingPublic.getSharingToken() : null;
OCA.Music.folderView.registerToNcFiles(ncFiles, sharingToken);
});
}).catch(_e => {/*ignore*/});

// The older fileActions API is used on ownCloud and NC27 and older, but also in NC28+ when operating
// The older fileActions API is used on ownCloud and NC27 and older, but also in NC28..30 when operating
// within a link-shared folder. It's also possible that we are operating within the share page of an
// individual file; this situation can be identified only after the page has been completely loaded.
window.addEventListener('DOMContentLoaded', () => {
const sharingToken = $('#sharingToken').val(); // undefined if not on a public share or on NC31+

if ($('#header').hasClass('share-file')) {
// individual link shared file
OCA.Music.fileShareView = new OCA.Music.FileShareView(mPlayer, mAudioMimes);
OCA.Music.fileShareView = new OCA.Music.FileShareView(mPlayer, mAudioMimes, sharingToken);
} else {
// Files app or a link shared folder
if (OCA.Files?.fileActions) {
OCA.Music.folderView.registerToFileActions(OCA.Files.fileActions);
OCA.Music.folderView.registerToFileActions(OCA.Files.fileActions, sharingToken);
}
OCA.Music.initPlaylistTabView(mPlaylistMimes);
connectPlaylistTabViewEvents(OCA.Music.folderView);
Expand Down
52 changes: 41 additions & 11 deletions lib/Controller/ShareController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@
* later. See the COPYING file.
*
* @author Pauli Järvinen <pauli.jarvinen@gmail.com>
* @copyright Pauli Järvinen 2018 - 2024
* @copyright Pauli Järvinen 2018 - 2025
*/

namespace OCA\Music\Controller;

use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\IRequest;
use OCP\Share\IManager;
use OCP\Share\Exceptions\ShareNotFound;

use OCA\Music\AppFramework\Core\Logger;
use OCA\Music\Http\ErrorResponse;
use OCA\Music\Http\FileStreamResponse;
use OCA\Music\Utility\PlaylistFileService;
use OCA\Music\Utility\Scanner;

Expand Down Expand Up @@ -84,18 +87,29 @@ public function fileInfo(string $token, int $fileId) {
* @PublicPage
* @NoCSRFRequired
*/
public function parsePlaylist(string $token, int $fileId) {
$share = $this->shareManager->getShareByToken($token);
$fileOwner = $share->getShareOwner();
$fileOwnerHome = $this->scanner->resolveUserFolder($fileOwner);

$matchingFolders = $fileOwnerHome->getById($share->getNodeId());

public function download(string $token, int $fileId) {
try {
$sharedFolder = $matchingFolders[0] ?? null;
if (!($sharedFolder instanceof Folder)) {
throw new \OCP\Files\NotFoundException();
$sharedFolder = $this->getSharedFolder($token);
$file = $sharedFolder->getById($fileId)[0] ?? null;
if ($file instanceof File) {
return new FileStreamResponse($file);
} else {
return new ErrorResponse(Http::STATUS_NOT_FOUND, 'no such file under the share');
}
} catch (ShareNotFound $ex) {
return new ErrorResponse(Http::STATUS_NOT_FOUND, 'invalid share token');
} catch (\OCP\Files\NotFoundException $ex) {
return new ErrorResponse(Http::STATUS_NOT_FOUND, 'the share is not a valid folder');
}
}

/**
* @PublicPage
* @NoCSRFRequired
*/
public function parsePlaylist(string $token, int $fileId) {
try {
$sharedFolder = $this->getSharedFolder($token);
$result = $this->playlistFileService->parseFile($fileId, $sharedFolder);

$bogusUrlId = -1;
Expand All @@ -120,10 +134,26 @@ public function parsePlaylist(string $token, int $fileId) {
}
}, $result['files']);
return new JSONResponse($result);
} catch (ShareNotFound $ex) {
return new ErrorResponse(Http::STATUS_NOT_FOUND, 'invalid share token');
} catch (\OCP\Files\NotFoundException $ex) {
return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist file not found');
} catch (\UnexpectedValueException $ex) {
return new ErrorResponse(Http::STATUS_UNSUPPORTED_MEDIA_TYPE, $ex->getMessage());
}
}

private function getSharedFolder(string $token) : Folder {
$share = $this->shareManager->getShareByToken($token);
$fileOwner = $share->getShareOwner();
$fileOwnerHome = $this->scanner->resolveUserFolder($fileOwner);

$matchingFolders = $fileOwnerHome->getById($share->getNodeId());
$folder = $matchingFolders[0] ?? null;
if (!($folder instanceof Folder)) {
throw new \OCP\Files\NotFoundException();
}

return $folder;
}
}
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"bugs": "https://github.com/owncloud/music/issues",
"dependencies": {
"@nextcloud/files": "^3.0.0",
"@nextcloud/sharing": "^0.2.4",
"angular": "^1.8.3",
"angular-gettext": "^2.4.2",
"angular-route": "^1.8.3",
Expand Down
6 changes: 6 additions & 0 deletions stubs/OC/HintException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

namespace OC;

class HintException extends \Exception {
}

0 comments on commit a69bb24

Please sign in to comment.