(смешное название коммита)

- Теперь на странице пользователя/группы показываются три случайные песни, а не первые три как раньше
- Теперь пробел на странице аудио не перемещает вас в низ страницы
- Оптимизирован мини-плеер, теперь он инициализируется при любом нажатии на него, а не при наведении
- Теперь при завершении проигрывания трека в мини-плеере он ищет другой трек рядом, и если находит то воспроизводит. Будет удобно для постов с подборками треков
- Поиск теперь показывает 14 результатов
- Теперь при возникновении ошибки загрузки аудио она нормально отображается
- Вместе с плеером на странице с аудиозаписями теперь двигаются и вкладки
- Добавление аудио в группу по идее должно нормально работать
This commit is contained in:
lalka2018 2023-10-30 13:12:04 +03:00
parent 4defb88846
commit edde634782
15 changed files with 200 additions and 33 deletions

View file

@ -120,14 +120,14 @@ class Audio extends Media
Shell::powershell("-executionpolicy bypass", "-File", __DIR__ . "/../shell/processAudio.ps1", ...$args)
->start();
} else {
exit("Linux uploads are not implemented");
throw new \BadMethodCallException("Linux uploads are not implemented");
}
# Wait until processAudio will consume the file
$start = time();
while(file_exists($filename))
if(time() - $start > 5)
exit("Timed out waiting for ffmpeg"); // TODO replace with exception
throw new \RuntimeException("Timed out waiting FFMPEG");
} catch(UnknownCommandException $ucex) {
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash/pwsh is not installed" : VIDEOS_FRIENDLY_ERROR);

View file

@ -222,4 +222,9 @@ class Playlist extends MediaCollection
return $cover;
}
function getURL(): string
{
return "/playlist" . $this->getOwner()->getRealId() . "_" . $this->getId();
}
}

View file

@ -109,6 +109,33 @@ class Audios
return $this->getByEntityID($user->getId(), ($perPage * ($page - 1)), $perPage, $deleted);
}
function getRandomThreeAudiosByEntityId(int $id): Array
{
$iter = $this->rels->where("entity", $id);
$ids = [];
foreach($iter as $it)
$ids[] = $it->audio;
$shuffleSeed = openssl_random_pseudo_bytes(6);
$shuffleSeed = hexdec(bin2hex($shuffleSeed));
$ids = knuth_shuffle($ids, $shuffleSeed);
$ids = array_slice($ids, 0, 3);
$audios = [];
foreach($ids as $id) {
$audio = $this->get((int)$id);
if(!$audio || $audio->isDeleted())
continue;
$audios[] = $audio;
}
return $audios;
}
function getByClub(Club $club, int $page = 1, ?int $perPage = NULL, ?int& $deleted = nullptr): \Traversable
{
return $this->getByEntityID($club->getId() * -1, ($perPage * ($page - 1)), $perPage, $deleted);

View file

@ -162,7 +162,30 @@ final class AudioPresenter extends OpenVKPresenter
} else {
$err = !isset($upload) ? 65536 : $upload["error"];
$err = str_pad(dechex($err), 9, "0", STR_PAD_LEFT);
$this->flashFail("err", tr("error"), tr("error_generic") . "Upload error: 0x$err", null, $isAjax);
$readableError = tr("error_generic");
switch($upload["error"]) {
default:
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$readableError = tr("file_too_big");
break;
case UPLOAD_ERR_PARTIAL:
$readableError = tr("file_loaded_partially");
break;
case UPLOAD_ERR_NO_FILE:
$readableError = tr("file_not_uploaded");
break;
case UPLOAD_ERR_NO_TMP_DIR:
$readableError = "Missing a temporary folder.";
break;
case UPLOAD_ERR_CANT_WRITE:
case UPLOAD_ERR_EXTENSION:
$readableError = "Failed to write file to disk. ";
break;
}
$this->flashFail("err", tr("error"), $readableError . " " . tr("error_code", $err), null, $isAjax);
}
$performer = $this->postParam("performer");
@ -186,6 +209,12 @@ final class AudioPresenter extends OpenVKPresenter
} catch(\DomainException $ex) {
$e = $ex->getMessage();
$this->flashFail("err", tr("error"), tr("media_file_corrupted_or_too_large") . " $e.", null, $isAjax);
} catch(\RuntimeException $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_timeout"), null, $isAjax);
} catch(\BadMethodCallException $ex) {
$this->flashFail("err", tr("error"), "Загрузка аудио под Linux на данный момент не реализована. Следите за обновлениями: <a href='https://github.com/openvk/openvk/pull/512/commits'>https://github.com/openvk/openvk/pull/512/commits</a>", null, $isAjax);
} catch(\Exception $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_not_installed"), null, $isAjax);
}
$audio->save();

View file

@ -31,7 +31,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
$this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
$this->template->audios = (new Audios)->getByClub($club, 1, 3);
$this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($club->getRealId());
$this->template->audiosCount = (new Audios)->getClubCollectionSize($club);
}

View file

@ -104,12 +104,13 @@ final class SearchPresenter extends OpenVKPresenter
$repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type.");
$results = $this->{$repo}->find($query, $parameters, $sort);
$iterator = $results->page($page);
$iterator = $results->page($page, 14);
$count = $results->size();
$this->template->iterator = iterator_to_array($iterator);
$this->template->count = $count;
$this->template->type = $type;
$this->template->page = $page;
$this->template->perPage = 14;
}
}

View file

@ -45,7 +45,7 @@ final class UserPresenter extends OpenVKPresenter
$this->template->videosCount = (new Videos)->getUserVideosCount($user);
$this->template->notes = (new Notes)->getUserNotes($user, 1, 4);
$this->template->notesCount = (new Notes)->getUserNotesCount($user);
$this->template->audios = (new Audios)->getByUser($user, 1, 3);
$this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($user->getId());
$this->template->audiosCount = (new Audios)->getUserCollectionSize($user);
$this->template->user = $user;

View file

@ -2,6 +2,23 @@
{block title}{_playlist}{/block}
{block headIncludes}
<meta property="og:type" content="music.album">
<meta property="og:title" content="{$playlist->getName()}">
<meta property="og:url" content="{$playlist->getURL()}">
<meta property="og:description" content="{$playlist->getDescription()}">
<meta property="og:image" content="{$playlist->getCoverURL()}">
<script type="application/ld+json">
{
"@context": "http://schema.org/",
"type": "MusicAlbum",
"name": {$playlist->getName()},
"url": {$playlist->getURL()}
}
</script>
{/block}
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»

View file

@ -1,7 +1,7 @@
{php $id = $audio->getId() . rand(0, 1000)}
{php $isWithdrawn = $audio->isWithdrawn()}
{php $editable = isset($thisUser) && $audio->canBeModifiedBy($thisUser)}
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$audio->isAvailable()}processed{/if} {if $isWithdrawn}withdrawn{/if}" onmouseenter="!this.classList.contains('inited') ? initPlayer({$id}, {$audio->getKeys()}, {$audio->getURL()}, {$audio->getLength()}) : void(0)">
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$audio->isAvailable()}processed{/if} {if $isWithdrawn}withdrawn{/if}" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
<audio class="audio" />
<div id="miniplayer" class="audioEntry" style="min-height: 39px;">

View file

@ -530,7 +530,7 @@
{var $musics = explode(", ", $user->getFavoriteMusic())}
{foreach $musics as $music}
<a href="/search?type=users&query=&fav_mus={urlencode($music)}">{$music}</a>{if $music != end($musics)},{/if}
<a href="/search?type=audios&query={urlencode($music)}">{$music}</a>{if $music != end($musics)},{/if}
{/foreach}
</td>
</tr>

View file

@ -558,4 +558,11 @@
.playlistInfo .playlistName {
font-weight: 600;
}
.searchList.floating {
position: fixed;
z-index: 199;
width: 156px;
margin-top: -65px !important;
}

View file

@ -270,7 +270,7 @@ class bigPlayer {
})
u(document).on("keydown", (e) => {
if(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
if(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", " "].includes(e.key)) {
e.preventDefault()
if(document.querySelector(".ovk-diag-cont") != null)
@ -290,11 +290,19 @@ class bigPlayer {
case "ArrowRight":
this.player().currentTime = this.player().currentTime + 3
break
// буквально
case " ":
if(this.player().paused)
this.play()
else
this.pause()
break;
}
})
u(document).on("keyup", (e) => {
if([32, 87, 65, 83, 68].includes(e.keyCode)) {
if([87, 65, 83, 68].includes(e.keyCode)) {
e.preventDefault()
if(document.querySelector(".ovk-diag-cont") != null)
@ -302,13 +310,6 @@ class bigPlayer {
}
switch(e.keyCode) {
case 32:
if(this.player().paused)
this.play()
else
this.pause()
break
case 87:
case 65:
this.showPreviousTrack()
@ -565,8 +566,10 @@ document.addEventListener("DOMContentLoaded", function() {
entries.forEach(x => {
if(x.isIntersecting) {
document.querySelector('.bigPlayer').classList.remove("floating")
document.querySelector('.searchOptions .searchList').classList.remove("floating")
document.querySelector('.bigPlayerDetector').style.marginTop = "0px"
} else {
document.querySelector('.searchOptions .searchList').classList.add("floating")
document.querySelector('.bigPlayer').classList.add("floating")
document.querySelector('.bigPlayerDetector').style.marginTop = "46px"
}
@ -581,6 +584,20 @@ document.addEventListener("DOMContentLoaded", function() {
bigPlayerObserver.observe(bigplayer);
}})
$(document).on("click", ".audioEmbed > *", (e) => {
const player = e.currentTarget.closest(".audioEmbed")
if(player.classList.contains("inited")) return
initPlayer(player.id.replace("audioEmbed-", ""),
JSON.parse(player.dataset.keys),
player.dataset.url,
player.dataset.length)
if(e.target.classList.contains("playIcon"))
e.target.click()
})
function initPlayer(id, keys, url, length) {
document.querySelector(`#audioEmbed-${ id}`).classList.add("inited")
const audio = document.querySelector(`#audioEmbed-${ id} .audio`);
@ -661,8 +678,40 @@ function initPlayer(id, keys, url, length) {
}
};
const hideTracks = () => {
$(`#audioEmbed-${ id} .track`).toggle()
$(`#audioEmbed-${ id}`).removeClass("havePlayed")
}
u(audio).on("play", playButtonImageUpdate);
u(audio).on(["pause", "ended", "suspended"], playButtonImageUpdate);
u(audio).on(["pause", "suspended"], playButtonImageUpdate);
u(audio).on("ended", (e) => {
let thisPlayer = e.target.closest(".audioEmbed")
let nextPlayer = null
if(thisPlayer.closest(".attachment") != null) {
try {
nextPlayer = thisPlayer.closest(".attachment").nextElementSibling.querySelector(".audioEmbed")
} catch(e) {return}
} else if(thisPlayer.closest(".audio") != null) {
try {
nextPlayer = thisPlayer.closest(".audio").nextElementSibling.querySelector(".audioEmbed")
} catch(e) {return}
} else {
nextPlayer = thisPlayer.nextElementSibling
}
playButtonImageUpdate()
if(!nextPlayer) return
initPlayer(nextPlayer.id.replace("audioEmbed-", ""),
JSON.parse(nextPlayer.dataset.keys),
nextPlayer.dataset.url,
nextPlayer.dataset.length)
nextPlayer.querySelector(".playIcon").click()
hideTracks()
})
u(`#audioEmbed-${ id} .lengthTrack > div`).on("click", (e) => {
let rect = document.querySelector("#audioEmbed-" + id + " .selectableTrack").getBoundingClientRect();
@ -835,7 +884,7 @@ $(document).on("click", ".musicIcon.remove-icon", (e) => {
hooks: {
beforeRequest: [
(_request) => {
e.currentTarget.classList.add("lagged")
e.target.classList.add("lagged")
}
],
afterResponse: [
@ -843,11 +892,11 @@ $(document).on("click", ".musicIcon.remove-icon", (e) => {
let json = await response.json()
if(json.success) {
e.currentTarget.classList.remove("remove-icon")
e.currentTarget.classList.add("add-icon")
e.currentTarget.classList.remove("lagged")
e.target.classList.remove("remove-icon")
e.target.classList.add("add-icon")
e.target.classList.remove("lagged")
let withd = e.currentTarget.closest(".audioEmbed.withdrawn")
let withd = e.target.closest(".audioEmbed.withdrawn")
if(withd != null)
u(withd).remove()
@ -920,13 +969,13 @@ $(document).on("click", ".musicIcon.add-icon-group", async (ev) => {
$(".ovk-diag-body").on("click", "input[name='addButton']", (e) => {
$.ajax({
type: "POST",
url: `/audio${ev.currentTarget.dataset.id}/action?act=add_to_club`,
url: `/audio${ev.target.dataset.id}/action?act=add_to_club`,
data: {
hash: u("meta[name=csrf]").attr("value"),
club: document.querySelector("#addIconsWindow").value
},
beforeSend: () => {
e.currentTarget.classList.add("lagged")
e.target.classList.add("lagged")
document.querySelector(".errorPlace").innerHTML = ""
},
success: (response) => {
@ -949,7 +998,7 @@ $(document).on("click", ".musicIcon.add-icon", (e) => {
hooks: {
beforeRequest: [
(_request) => {
e.currentTarget.classList.add("lagged")
e.target.classList.add("lagged")
}
],
afterResponse: [
@ -957,9 +1006,9 @@ $(document).on("click", ".musicIcon.add-icon", (e) => {
let json = await response.json()
if(json.success) {
e.currentTarget.classList.remove("add-icon")
e.currentTarget.classList.add("remove-icon")
e.currentTarget.classList.remove("lagged")
e.target.classList.remove("add-icon")
e.target.classList.add("remove-icon")
e.target.classList.remove("lagged")
} else
fastError(json.flash.message)
}

View file

@ -39,6 +39,7 @@ CREATE TABLE IF NOT EXISTS `audio_listens` (
`audio` bigint unsigned NOT NULL,
`time` bigint unsigned NOT NULL,
`index` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Workaround for Nette DBE bug',
`playlist` bigint(20) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`index`),
KEY `audio` (`audio`),
KEY `user` (`entity`) USING BTREE,
@ -63,6 +64,7 @@ CREATE TABLE IF NOT EXISTS `playlists` (
`length` int unsigned NOT NULL DEFAULT '0',
`special_type` tinyint unsigned NOT NULL DEFAULT '0',
`created` bigint unsigned DEFAULT NULL,
`listens` bigint(20) unsigned NOT NULL DEFAULT 0,
`edited` bigint unsigned DEFAULT NULL,
`deleted` tinyint unsigned DEFAULT '0',
PRIMARY KEY (`id`),

View file

@ -1402,6 +1402,12 @@
"playlist_not_bookmarked" = "This playlist is not in your collection.";
"invalid_cover_photo" = "Error when loading cover photo.";
"not_a_photo" = "Uploaded file doesn't look like a photo.";
"file_too_big" = "File is too big.";
"file_loaded_partially" = "The file has been uploaded partially.";
"file_not_uploaded" = "Failed to upload the file.";
"error_code" = "Error code: $1.";
"ffmpeg_timeout" = "Timed out waiting ffmpeg. Try to upload file again.";
"ffmpeg_not_installed" = "Failed to proccess the file. It looks like ffmpeg is not installed on this server.";
/* Admin actions */
@ -1736,8 +1742,17 @@
"tour_section_5_text_3" = "In addition to uploading videos directly, the site also supports embedding videos from YouTube";
"tour_section_6_title_1" = "Audios section, which doesn't exist yet xdddd";
"tour_section_6_text_1" = "I would love to do a tutorial on this section, but sunshine Vriska didn't make the music :c";
"tour_section_6_title_1" = "Listen to audios";
"tour_section_6_text_1" = "Вы можете слушать аудиозаписи в разделе \"Мои Аудиозаписи\".";
"tour_section_6_text_2" = "Этот раздел также регулируется настройками приватности.";
"tour_section_6_text_3" = "Самые прослушиваемые песни находятся во вкладке \"Популярное\", а недавно загруженные — во вкладке \"Новое\"";
"tour_section_6_text_4" = "Найти нужную песню можно в поиске.";
"tour_section_6_text_5" = "Чтобы добавить песню в свою коллекцию, наведите на неё и нажмите на плюс.";
"tour_section_6_text_6" = "Если вы не можете найти нужную песню, вы можете загрузить её самостоятельно.";
"tour_section_6_bottom_text_1" = "<b>Важно:</b> песня не должна нарушать авторские права";
"tour_section_6_title_1" = "Создавайте плейлисты";
"tour_section_6_text_7" = "Вы можете создавать сборники треков во вкладке \"Мои плейлисты\".";
"tour_section_6_text_8" = "Можно также добавлять чужие плейлисты в свою коллекцию.";
"tour_section_7_title_1" = "Follow what your friends write";

View file

@ -1298,6 +1298,12 @@
"playlist_not_bookmarked" = "Плейлиста нет в вашей коллекции.";
"invalid_cover_photo" = "Не удалось сохранить обложку плейлиста.";
"not_a_photo" = "Загруженный файл не похож на фотографию.";
"file_too_big" = "Файл слишком большой.";
"file_loaded_partially" = "Файл загрузился частично.";
"file_not_uploaded" = "Не удалось загрузить файл.";
"error_code" = "Код ошибки: $1.";
"ffmpeg_timeout" = "Превышено время ожидания обработки ffmpeg. Попробуйте загрузить файл снова.";
"ffmpeg_not_installed" = "Не удалось обработать файл. Похоже, на сервере не установлен ffmpeg.";
/* Admin actions */
@ -1625,8 +1631,17 @@
"tour_section_5_text_3" = "Кроме загрузки видео напрямую, сайт поддерживает и встраивание видео из YouTube";
"tour_section_6_title_1" = "Аудиозаписи, которых пока что нет XD";
"tour_section_6_text_1" = "Я был бы очень рад сделать туториал по этому разделу, но солнышко Вриска не сделала музыку";
"tour_section_6_title_1" = "Слушайте аудиозаписи";
"tour_section_6_text_1" = "Вы можете слушать аудиозаписи в разделе \"Мои Аудиозаписи\".";
"tour_section_6_text_2" = "Этот раздел также регулируется настройками приватности.";
"tour_section_6_text_3" = "Самые прослушиваемые песни находятся во вкладке \"Популярное\", а недавно загруженные — во вкладке \"Новое\"";
"tour_section_6_text_4" = "Найти нужную песню можно в поиске.";
"tour_section_6_text_5" = "Чтобы добавить песню в свою коллекцию, наведите на неё и нажмите на плюс.";
"tour_section_6_text_6" = "Если вы не можете найти нужную песню, вы можете загрузить её самостоятельно.";
"tour_section_6_bottom_text_1" = "<b>Важно:</b> песня не должна нарушать авторские права";
"tour_section_6_title_1" = "Создавайте плейлисты";
"tour_section_6_text_7" = "Вы можете создавать сборники треков во вкладке \"Мои плейлисты\".";
"tour_section_6_text_8" = "Можно также добавлять чужие плейлисты в свою коллекцию.";
"tour_section_7_title_1" = "Следите за тем, что пишут ваши друзья";