Add audio queue

This commit is contained in:
lalka2018 2023-10-14 22:05:57 +03:00
parent 68e16cfbb0
commit d227a14a10
10 changed files with 458 additions and 25 deletions

View file

@ -298,7 +298,9 @@ final class AudioPresenter extends OpenVKPresenter
$this->notFound();
$this->template->playlist = $playlist;
$this->template->page = (int)($this->queryParam("p") ?? 0);
$this->template->audios = iterator_to_array($playlist->getAudios());
$this->template->isBookmarked = $playlist->isBookmarkedBy($this->user->identity);
$this->template->isMy = $playlist->getOwner()->getId() === $this->user->id;
$this->template->canEdit = ($this->template->isMy || ($playlist->getOwner() instanceof Club && $playlist->getOwner()->canBeModifiedBy($this->user->identity)));
$this->template->edit = $this->queryParam("act") === "edit";
@ -453,4 +455,86 @@ final class AudioPresenter extends OpenVKPresenter
{
$this->renderList($owner, "playlists");
}
function renderApiGetContext()
{
$this->assertUserLoggedIn();
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("<select><select><select><select>");
}
$ctx_type = $this->postParam("context");
$ctx_id = (int)($this->postParam("context_entity"));
$page = (int)($this->postParam("page") ?? 1);
$perPage = 10;
switch($ctx_type) {
default:
case "entity_audios":
if($ctx_id > 0) {
$entity = (new Users)->get($ctx_id);
if(!$entity || !$entity->getPrivacyPermission("audios.read", $this->user->identity))
$this->flashFail("err", "Error", "Can't get queue", 80, true);
$audios = $this->audios->getByUser($entity, $page, $perPage);
$audiosCount = $this->audios->getUserCollectionSize($entity);
} else {
$entity = (new Clubs)->get(abs($ctx_id));
if(!$entity || $entity->isBanned())
$this->flashFail("err", "Error", "Can't get queue", 80, true);
$audios = $this->audios->getByClub($entity, $page, $perPage);
$audiosCount = $this->audios->getClubCollectionSize($entity);
}
break;
case "new_audios":
$audios = $this->audios->getNew();
$audiosCount = $audios->size();
break;
case "popular_audios":
$audios = $this->audios->getPopular();
$audiosCount = $audios->size();
break;
case "playlist_context":
$playlist = $this->audios->getPlaylist($ctx_id);
if (!$playlist || $playlist->isDeleted())
$this->flashFail("err", "Error", "Can't get queue", 80, true);
$audios = $playlist->getAudios($page, 10);
$audiosCount = $playlist->size();
break;
}
$pagesCount = ceil($audiosCount / $perPage);
$audiosArr = [];
foreach($audios as $audio) {
$audiosArr[] = [
"id" => $audio->getId(),
"name" => $audio->getTitle(),
"performer" => $audio->getPerformer(),
"keys" => $audio->getKeys(),
"url" => $audio->getUrl(),
"length" => $audio->getLength(),
"available" => $audio->isAvailable(),
"withdrawn" => $audio->isWithdrawn(),
];
}
$resultArr = [
"page" => $page,
"perPage" => $perPage,
"pagesCount" => $pagesCount,
"count" => $audiosCount,
"items" => $audiosArr,
];
$this->returnJson($resultArr);
}
}

View file

@ -35,6 +35,11 @@
{* ref: https://archive.li/P32em *}
{include "bigplayer.xml"}
<input n:if="$mode == 'list'" type="hidden" name="bigplayer_context" data-type="entity_audios" data-entity="{$ownerId}" data-page="{$page}">
<input n:if="$mode == 'new'" type="hidden" name="bigplayer_context" data-type="new_audios" data-entity="0" data-page="1">
<input n:if="$mode == 'popular'" type="hidden" name="bigplayer_context" data-type="popular_audios" data-entity="0" data-page="1">
<div style="width: 100%;display: flex;margin-bottom: -10px;">
<div style="width: 74%;" n:if="$mode != 'playlists'">
<div style="padding: 8px;">
@ -47,7 +52,7 @@
</div>
</div>
<div>
<div n:if="$mode != 'new' && $mode != 'popular'">
{include "../components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $audiosCount,

View file

@ -12,12 +12,16 @@
{include "bigplayer.xml"}
{php $count = $playlist->size()}
<input type="hidden" name="bigplayer_context" data-type="playlist_context" data-entity="{$playlist->getId()}" data-page="{$page}">
<div class="playlistBlock">
<div class="playlistCover" style="float: left;">
<img src="{$playlist->getCoverURL()}">
<div class="profile_links">
<a id="profile_link">{_edit_playlist}</a>
<div class="profile_links" style="width: 139px;">
<a id="profile_link" style="width: 98%;" n:if="$canEdit">{_edit_playlist}</a>
<a id="profile_link" style="width: 98%;" id="bookmarkPlaylist" n:if="$isBookmarked">{_unbookmark}</a>
<a id="profile_link" style="width: 98%;" id="unbookmarkPlaylist" n:if="!$isBookmarked">{_bookmark}</a>
</div>
</div>

View file

@ -1,4 +1,5 @@
<div class="bigPlayer">
<audio class="audio" />
<div class="paddingLayer">
<div class="playButtons">
<div class="playButton musicIcon"></div>
@ -17,16 +18,18 @@
<span class="time">00:00</span>
</div>
<div class="selectableTrack">
<div>&nbsp;
<div class="slider"></div>
<div class="track">
<div class="selectableTrack">
<div style="width: 95%;position: relative;">&nbsp;
<div class="slider"></div>
</div>
</div>
</div>
</div>
<div class="volumePanel">
<div class="selectableTrack">
<div>&nbsp;
<div style="position: relative;width:74%">&nbsp;
<div class="slider"></div>
</div>
</div>

View file

@ -12,15 +12,13 @@
<div class="playIcon"></div>
</div>
<div class="status" style="margin-top: 9px;">
<div class="status" style="margin-top: 11px;">
<div class="mediaInfo" style="margin-bottom: -8px; cursor: pointer;">
<strong class="performer">
<a style="color: unset !important;" href="/search?query=&type=audios&sort=id&only_performers=on&query={$audio->getPerformer()}">{ovk_proc_strtr($audio->getPerformer(), 50)}</a>
<a style="color: unset" href="/search?query=&type=audios&sort=id&only_performers=on&query={$audio->getPerformer()}">{ovk_proc_strtr($audio->getPerformer(), 50)}</a>
</strong>
<span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">
{ovk_proc_strtr($audio->getTitle(), 50)}
</span>
<span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">{ovk_proc_strtr($audio->getTitle(), 50)}</span>
<svg n:if="$audio->isExplicit()" xmlns="http://www.w3.org/2000/svg" height="11" viewBox="0 0 11 11" width="11">
<path d="m1 2.506v5.988a1.5 1.5 0 0 0 1.491 1.506h6.019c.827 0 1.49-.674 1.49-1.506v-5.988a1.5 1.5 0 0 0 -1.491-1.506h-6.019c-.827 0-1.49.674-1.49 1.506zm4 2.494v-1h2v-1h-3v5h3v-1h-2v-1h2v-1zm-5-2.494a2.496 2.496 0 0 1 2.491-2.506h6.019a2.5 2.5 0 0 1 2.49 2.506v5.988a2.496 2.496 0 0 1 -2.491 2.506h-6.019a2.5 2.5 0 0 1 -2.49-2.506z"
fill="#828a99" fill-opacity=".7"/>
@ -40,9 +38,7 @@
</div>
<div class="volume" style="display: flex; flex-direction: column;width:14%;">
<span class="nobold" style="text-align: center;margin-top: 12px;">
{$audio->getFormattedLength()}
</span>
<span class="nobold" data-unformatted="{$audio->getLength()}" style="text-align: center;margin-top: 12px;">{$audio->getFormattedLength()}</span>
<div class="buttons" style="margin-top: 8px;">
{php $hasAudio = $audio->isInLibraryOf($thisUser)}

View file

@ -2,7 +2,7 @@
<ul class="searchList">
<a n:attr="id => $mode === 'list' && $isMy ? 'used' : 'ki'" href="/audios{$thisUser->getId()}">{_my_music}</a>
<a n:if="!$isMy" n:attr="id => $mode === 'list' ? 'used' : 'ki'" href="/audios{$ownerId}">{if $ownerId > 0}{_music_user}{else}{_music_club}{/if}</a>
<a n:if="!$isMy && $mode === 'list'" id="used" href="/audios{$ownerId}">{if $ownerId > 0}{_music_user}{else}{_music_club}{/if}</a>
<a href="/player/upload{if $isMyClub}?gid={abs($ownerId)}{/if}">{_upload_audio}</a>

View file

@ -207,6 +207,8 @@ routes:
handler: "Audio->search"
- url: "/audios/newPlaylist"
handler: "Audio->newPlaylist"
- url: "/audios/context"
handler: "Audio->apiGetContext"
- url: "/playlist{num}_{num}"
handler: "Audio->playlist"
- url: "/playlists{num}"

View file

@ -4,6 +4,10 @@
cursor: pointer;
}
.musicIcon.pressed {
filter: brightness(59%);
}
.bigPlayer {
background-color: rgb(240, 241, 242);
margin-left: -10px;
@ -28,6 +32,10 @@
float: left;
}
.bigPlayer .paddingLayer .playButtons .playButton.pause {
background-position-x: -144px;
}
.bigPlayer .paddingLayer .playButtons .nextButton {
width: 16px;
height: 16px;
@ -196,6 +204,35 @@
border: 1px solid #8B8B8B;
}
.audioEntry.nowPlaying {
background: #787878;
border: 1px solid #4f4f4f;
}
.audioEntry.nowPlaying:hover {
background: rgb(100, 100, 100) !important;
}
.audioEntry.nowPlaying .performer a {
color: #f4f4f4 !important;
}
.audioEntry.nowPlaying .title {
color: #fff;
}
.audioEntry.nowPlaying .status {
color: white;
}
.audioEntry.nowPlaying .volume .nobold {
color: white !important;
}
.audioEntry.nowPlaying .buttons .musicIcon {
filter: brightness(187%) opacity(72%);
}
.audioEntry {
display: flex;
height: 100%;

View file

@ -2850,7 +2850,7 @@ body.article .floating_sidebar, body.article .page_content {
.lagged {
filter: opacity(0.5);
cursor: progress;
cursor: not-allowed;
user-select: none;
}
@ -2864,7 +2864,7 @@ body.article .floating_sidebar, body.article .page_content {
pointer-events: none;
}
.lagged * {
.lagged *, .lagged {
pointer-events: none;
}

View file

@ -4,6 +4,286 @@ function fmtTime(time) {
return `${ mins}:${ secs}`;
}
// нихуя я тут насрал
class bigPlayer {
contextObject = []
player = null
currentTrack = null
dashPlayer = null
constructor(context, context_id, page = 1) {
this.context = context
this.context_id = context_id
this.playerNode = document.querySelector(".bigPlayer")
this.performer = this.playerNode.querySelector(".trackInfo b")
this.name = this.playerNode.querySelector(".trackInfo span")
this.time = this.playerNode.querySelector(".trackInfo .time")
this.player = () => { return this.playerNode.querySelector("audio.audio") }
this.playButtons = this.playerNode.querySelector(".playButtons")
this.dashPlayer = dashjs.MediaPlayer().create()
let formdata = new FormData()
formdata.append("context", context)
formdata.append("context_entity", context_id)
formdata.append("hash", u("meta[name=csrf]").attr("value"))
formdata.append("page", page)
ky.post("/audios/context", {
hooks: {
afterResponse: [
async (_request, _options, response) => {
this.contextObject = await response.json()
console.info("Context is successfully loaded")
}
]
},
body: formdata
})
u(this.playButtons.querySelector(".playButton")).on("click", (e) => {
if(this.player().paused) {
this.play()
} else {
this.pause()
}
})
u(this.player()).on("timeupdate", (e) => {
const time = this.player().currentTime;
const ps = Math.ceil((time * 100) / this.currentTrack.length);
this.time.innerHTML = fmtTime(time)
if (ps <= 100)
this.playerNode.querySelector(".selectableTrack .slider").style.left = `${ ps}%`;
})
u(this.player()).on("volumechange", (e) => {
const volume = this.player().volume;
const ps = Math.ceil((volume * 100) / 1);
if (ps <= 100)
this.playerNode.querySelector(".volumePanel .selectableTrack .slider").style.left = `${ ps}%`;
})
u(".bigPlayer .track > div").on("click mouseup", (e) => {
if(this.currentTrack == null) {
return
}
let rect = this.playerNode.querySelector(".selectableTrack").getBoundingClientRect();
const width = e.clientX - rect.left;
const time = Math.ceil((width * this.currentTrack.length) / (rect.right - rect.left));
this.player().currentTime = time;
})
u(".bigPlayer .volumePanel > div").on("click mouseup", (e) => {
if(this.currentTrack == null) {
return
}
let rect = this.playerNode.querySelector(".volumePanel .selectableTrack").getBoundingClientRect();
const width = e.clientX - rect.left;
const volume = (width * 1) / (rect.right - rect.left);
this.player().volume = volume;
})
u(".bigPlayer .additionalButtons .repeatButton").on("click", (e) => {
if(this.currentTrack == null) {
return
}
e.currentTarget.classList.toggle("pressed")
})
u(".bigPlayer .arrowsButtons .nextButton").on("click", (e) => {
this.showPreviousTrack()
})
u(".bigPlayer .arrowsButtons .backButton").on("click", (e) => {
this.showNextTrack()
})
u(this.player()).on("ended", (e) => {
e.preventDefault()
// код не работает, как я понял, оно не хочет нажимать потому что это не действие пользователя
if(this.playerNode.querySelector(".repeatButton").classList.contains("pressed")) {
this.player().currentTime = 0
this.player().play()
} else {
this.showNextTrack()
}
})
this.player().volume = 0.75
}
play() {
if(this.currentTrack == null) {
return
}
document.querySelectorAll('audio').forEach(el => el.pause());
document.querySelector(`.audioEmbed[data-realid='${this.currentTrack.id}'] .audioEntry .playerButton .playIcon`) != null ? document.querySelector(`.audioEmbed[data-realid='${this.currentTrack.id}'] .audioEntry .playerButton .playIcon`).classList.add("paused") : void(0)
this.player().play()
this.playButtons.querySelector(".playButton").classList.add("pause")
}
pause() {
if(this.currentTrack == null) {
return
}
document.querySelector(`.audioEmbed[data-realid='${this.currentTrack.id}'] .audioEntry .playerButton .playIcon`) != null ? document.querySelector(`.audioEmbed[data-realid='${this.currentTrack.id}'] .audioEntry .playerButton .playIcon`).classList.remove("paused") : void(0)
this.player().pause()
this.playButtons.querySelector(".playButton").classList.remove("pause")
}
showPreviousTrack() {
if(this.currentTrack == null || this.previousTrack == null) {
return
}
this.setTrack(this.previousTrack)
}
showNextTrack() {
if(this.currentTrack == null || this.nextTrack == null) {
return
}
this.setTrack(this.nextTrack)
}
updateButtons() {
// перепутал некст и бек.
let prevButton = this.playerNode.querySelector(".nextButton")
let nextButton = this.playerNode.querySelector(".backButton")
if(this.previousTrack == null) {
prevButton.classList.add("lagged")
} else {
prevButton.classList.remove("lagged")
}
if(this.nextTrack == null) {
nextButton.classList.add("lagged")
} else {
nextButton.classList.remove("lagged")
}
}
setTrack(id) {
if(this.contextObject["items"] == null) {
console.info("Context is not loaded yet. Wait please")
return 0;
}
document.querySelectorAll(".audioEntry.nowPlaying").forEach(el => el.classList.remove("nowPlaying"))
let obj = this.contextObject["items"].find(item => item.id == id)
this.name.innerHTML = obj.name
this.performer.innerHTML = obj.performer
this.time.innerHTML = fmtTime(obj.length)
this.currentTrack = obj
let indexOfCurrentTrack = this.contextObject["items"].indexOf(obj) ?? 0
this.nextTrack = this.contextObject["items"].at(indexOfCurrentTrack + 1) != null ? this.contextObject["items"].at(indexOfCurrentTrack + 1).id : null
if(indexOfCurrentTrack - 1 >= 0) {
this.previousTrack = this.contextObject["items"].at(indexOfCurrentTrack - 1).id
} else {
this.previousTrack = null
}
// todo поменьше копипастить код
if(this.nextTrack == null && this.contextObject.page < this.contextObject.pagesCount
|| this.previousTrack == null && (this.contextObject.page > 1)) {
let formdata = new FormData()
formdata.append("context", this.context)
formdata.append("context_entity", this.context_id)
formdata.append("hash", u("meta[name=csrf]").attr("value"))
let lesser = this.contextObject.page > 1
if(lesser) {
formdata.append("page", Number(this.contextObject["page"]) - 1)
} else {
formdata.append("page", Number(this.contextObject["page"]) + 1)
}
ky.post("/audios/context", {
hooks: {
afterResponse: [
async (_request, _options, response) => {
let newArr = await response.json()
if(lesser) {
this.contextObject["items"] = newArr["items"].concat(this.contextObject["items"])
} else {
this.contextObject["items"] = this.contextObject["items"].concat(newArr["items"])
}
this.contextObject["page"] = newArr["page"]
if(lesser) {
this.previousTrack = this.contextObject["items"].at(this.contextObject["items"].indexOf(obj) - 1).id
} else {
this.nextTrack = this.contextObject["items"].at(indexOfCurrentTrack + 1) != null ? this.contextObject["items"].at(indexOfCurrentTrack + 1).id : null
}
this.updateButtons()
console.info("Context is successfully loaded")
}
]
},
body: formdata
})
}
if(this.currentTrack.available == false || this.currentTrack.withdrawn) {
this.showNextTrack()
}
this.updateButtons()
const protData = {
"org.w3.clearkey": {
"clearkeys": obj.keys
}
};
this.dashPlayer.initialize(this.player(), obj.url, false);
this.dashPlayer.setProtectionData(protData);
this.play()
document.querySelector(`.audioEmbed[data-realid='${this.currentTrack.id}'] .audioEntry`) != null ?
document.querySelector(`.audioEmbed[data-realid='${this.currentTrack.id}'] .audioEntry`).classList.add("nowPlaying") :
null
document.querySelectorAll(`.audioEntry .playerButton .playIcon.paused`).forEach(el => el.classList.remove("paused"))
}
getCurrentTrack() {
return this.currentTrack
}
}
document.addEventListener("DOMContentLoaded", function() {
if(document.querySelector(".bigPlayer") != null) {
let context = document.querySelector("input[name='bigplayer_context']")
let type = context.dataset.type
let entity = context.dataset.entity
window.player = new bigPlayer(type, entity, context.dataset.page)
}})
function initPlayer(id, keys, url, length) {
document.querySelector(`#audioEmbed-${ id}`).classList.add("inited")
const audio = document.querySelector(`#audioEmbed-${ id} .audio`);
@ -11,7 +291,25 @@ function initPlayer(id, keys, url, length) {
const trackDiv = u(`#audioEmbed-${ id} .track > div > div`);
const volumeSpan = u(`#audioEmbed-${ id} .volume span`);
const rect = document.querySelector(`#audioEmbed-${ id} .selectableTrack`).getBoundingClientRect();
const playerObject = document.querySelector(`#audioEmbed-${ id}`)
if(document.querySelector(".bigPlayer") != null) {
playButton.on("click", () => {
if(window.player.contextObject == null) {
return
}
if(window.player.getCurrentTrack() == null || window.player.getCurrentTrack().id != playerObject.dataset.realid) {
window.player.setTrack(playerObject.dataset.realid)
} else {
document.querySelector(".bigPlayer .playButton").click()
}
})
return
}
const protData = {
"org.w3.clearkey": {
"clearkeys": keys
@ -44,15 +342,11 @@ function initPlayer(id, keys, url, length) {
});
const playButtonImageUpdate = () => {
if ($(`#audioEmbed-${ id} .claimed`).length === 0) {
console.log(id);
}
if (!audio.paused) {
playButton.addClass("paused")
$.post(`/audio${ id}/listen`, {
/*$.post(`/audio${ id}/listen`, {
hash: u("meta[name=csrf]").attr("value")
});
});*/
} else {
playButton.removeClass("paused")
}
@ -71,7 +365,7 @@ function initPlayer(id, keys, url, length) {
let rect = document.querySelector("#audioEmbed-" + id + " .selectableTrack").getBoundingClientRect();
const width = e.clientX - rect.left;
const time = Math.ceil((width * length) / (rect.right - rect.left));
console.log(width, length, rect.right, rect.left, time);
audio.currentTime = time;
});
}
@ -274,4 +568,12 @@ $(document).on("click", ".musicIcon.report-icon", (e) => {
}),
Function.noop])
})
$(document).on("click", "#bookmarkPlaylist", (e) => {
})
$(document).on("click", "#unbookmarkPlaylist", (e) => {
})