feat: huge feature: a floating player, with a movable playhead

This commit is contained in:
Terry Geng committed 2020-05-17 11:54:05 +08:00
1 parent b050546e39
commit 0b7d0b8465
9 files changed
+341 -35

No files matched your search

+16 -4
View File
@@ -194,7 +194,7 @@ class Condition:
SETTING_DB_VERSION = 2
MUSIC_DB_VERSION = 2
MUSIC_DB_VERSION = 3
class SettingsDatabase:
@@ -503,7 +503,8 @@ class DatabaseMigration:
self.settings_table_migrate_func = {0: self.settings_table_migrate_from_0_to_1,
1: self.settings_table_migrate_from_1_to_2}
self.music_table_migrate_func = {0: self.music_table_migrate_from_0_to_1,
1: self.music_table_migrate_from_1_to_2}
1: self.music_table_migrate_from_1_to_2,
2: self.music_table_migrate_from_2_to_3}
def migrate(self):
self.settings_database_migrate()
@@ -564,7 +565,7 @@ class DatabaseMigration:
else:
log.info(f"database: no music table found. Creating music table version {MUSIC_DB_VERSION}.")
self.create_music_table_version_2(conn)
self.create_music_table_version_3(conn)
conn.commit()
conn.close()
@@ -607,7 +608,7 @@ class DatabaseMigration:
conn.commit()
def create_music_table_version_2(self, conn):
def create_music_table_version_3(self, conn):
self.create_music_table_version_1(conn)
def settings_table_migrate_from_0_to_1(self, conn):
@@ -669,3 +670,14 @@ class DatabaseMigration:
conn.commit()
return 2 # return new version number
def music_table_migrate_from_2_to_3(self, conn):
items_to_update = self.music_db.query_music(Condition(), conn)
for item in items_to_update:
if 'duration' not in item:
item['duration'] = 0
self.music_db.insert_music(item)
conn.commit()
return 3 # return new version number
+15 -2
View File
@@ -196,15 +196,19 @@ def playlist():
title = item.format_title()
artist = "??"
path = ""
duration = 0
if isinstance(item, FileItem):
path = item.path
if item.artist:
artist = item.artist
duration = item.duration
elif isinstance(item, URLItem):
path = f" <a href=\"{item.url}\"><i>{item.url}</i></a>"
duration = item.duration
elif isinstance(item, PlaylistURLItem):
path = f" <a href=\"{item.url}\"><i>{item.url}</i></a>"
artist = f" <a href=\"{item.playlist_url}\"><i>{item.playlist_title}</i></a>"
duration = item.duration
elif isinstance(item, RadioItem):
path = f" <a href=\"{item.url}\"><i>{item.url}</i></a>"
@@ -223,6 +227,7 @@ def playlist():
'artist': artist,
'thumbnail': thumb,
'tags': tag_tuples,
'duration': duration
})
return jsonify({
@@ -240,7 +245,9 @@ def status():
'empty': False,
'play': not var.bot.is_pause,
'mode': var.playlist.mode,
'volume': var.bot.volume_set})
'volume': var.bot.volume_set,
'playhead': var.bot.playhead
})
else:
return jsonify({'ver': var.playlist.version,
@@ -248,7 +255,9 @@ def status():
'empty': True,
'play': not var.bot.is_pause,
'mode': var.playlist.mode,
'volume': var.bot.volume_set})
'volume': var.bot.volume_set,
'playhead': 0
})
@web.route("/post", methods=['POST'])
@@ -335,6 +344,10 @@ def post():
if len(var.playlist) >= int(request.form['play_music']):
var.bot.play(int(request.form['play_music']))
time.sleep(0.1)
elif 'move_playhead' in request.form:
if float(request.form['move_playhead']) < var.playlist.current_item().item().duration:
log.info(f"web: move playhead to {float(request.form['move_playhead'])} s.")
var.bot.play(var.playlist.current_index, float(request.form['move_playhead']))
elif 'delete_item_from_library' in request.form:
_id = request.form['delete_item_from_library']
+7 -2
View File
@@ -6,6 +6,7 @@ import hashlib
import mutagen
from PIL import Image
import util
import variables as var
from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError
import constants
@@ -51,6 +52,7 @@ class FileItem(BaseItem):
if os.path.exists(self.uri()):
self._get_info_from_tag()
self.ready = "yes"
self.duration = util.get_media_duration(self.uri())
self.keywords = self.title + " " + self.artist
else:
super().__init__(from_dict)
@@ -75,7 +77,9 @@ class FileItem(BaseItem):
"file: music file missed for %s" % self.format_debug_string())
raise ValidationFailedError(constants.strings('file_missed', file=self.path))
# self.version += 1 # 0 -> 1, notify the wrapper to save me when validate() is visited the first time
if self.duration == 0:
self.duration = util.get_media_duration(self.uri())
self.version += 1 # 0 -> 1, notify the wrapper to save me
self.ready = "yes"
return True
@@ -130,7 +134,8 @@ class FileItem(BaseItem):
if not self.title:
self.title = os.path.basename(file_no_ext)
def _prepare_thumbnail(self, im):
@staticmethod
def _prepare_thumbnail(im):
im.thumbnail((100, 100), Image.ANTIALIAS)
buffer = BytesIO()
im = im.convert('RGB')
+4 -1
View File
@@ -50,6 +50,7 @@ class BaseItem:
self.path = ""
self.tags = []
self.keywords = ""
self.duration = 0
self.version = 0 # if version increase, wrapper will re-save this item
if from_dict is None:
@@ -62,6 +63,7 @@ class BaseItem:
self.title = from_dict['title']
self.path = from_dict['path']
self.keywords = from_dict['keywords']
self.duration = from_dict['duration']
def is_ready(self):
return True if self.ready == "yes" else False
@@ -117,4 +119,5 @@ class BaseItem:
"title": self.title,
"path": self.path,
"tags": self.tags,
"keywords": self.keywords}
"keywords": self.keywords,
"duration": self.duration}
+1 -1
View File
@@ -3,4 +3,4 @@
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unsLine truncated
*/:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unsLine truncated
+67 -3
View File
@@ -86,7 +86,7 @@
font-weight: 300;
}
/* Theme changer */
/* Theme changer and player button */
.floating-button {
width: 50px;
height: 50px;
@@ -100,7 +100,6 @@
line-height: 52px;
position: fixed;
right: 50px;
bottom: 40px;
}
.floating-button:hover {
background-color: hsl(0, 0%, 43%);
@@ -122,7 +121,6 @@
font-size: 20px;
border-radius: 4px;
display: none;
/* margin-bottom: 5px; */
}
#volume-popover[data-show] {
display: flex;
@@ -148,3 +146,69 @@
#volume-popover[data-popper-placement^='top'] > #volume-popover-arrow {
bottom: -4px;
}
#playerToast {
position: fixed;
right: 20px;
top: 20px;
max-width: 800px;
}
#playerContainer {
display: flex;
height: 105px;
}
#playerArtwork {
width: 80px;
height: 80px;
border-radius: 5px;
}
#playerArtworkIdle {
width: 80px;
height: 80px;
border-radius: 5px;
margin: auto;
padding: 15px;
}
#playerInfo {
position: relative;
padding-top: 6px;
margin-left: 10px;
height: 80px;
font-size: 15px;
}
#playerTitle {
display: block;
white-space: nowrap;
}
#playerArtist {
display: block;
white-space: nowrap;
min-height: 20px;
}
#playerActionBox {
margin-top: 5px;
display: flex;
float: right;
}
#playerBarBox {
margin-top: 5px;
height: 15px;
width: 400px;
cursor: pointer;
}
.scrolling {
animation: scrolling 8s linear infinite;
}
@keyframes scrolling {
0% {
transform: translateX(100%);
opacity: 1;
}
95%{
transform: translateX(-90%);
opacity: 1;
}
100% {
transform: translateX(-100%);
opacity: 0;
}
}
+169 -20
View File
@@ -31,6 +31,8 @@ const playlist_table = $("#playlist-table");
const playlist_empty = $("#playlist-empty");
const playlist_expand = $(".playlist-expand");
let playlist_items = null;
let playlist_ver = 0;
let playlist_current_index = 0;
@@ -69,6 +71,7 @@ function request(_url, _data, refresh = false) {
checkForPlaylistUpdate();
}
updateControls(data.empty, data.play, data.mode, data.volume);
updatePlayerPlayhead(data.playhead);
}
},
});
@@ -119,6 +122,7 @@ function displayPlaylist(data) {
playlist_loading.hide();
$(".playlist-item").remove();
let items = data.items;
playlist_items = items;
let length = data.length;
let start_from = data.start_from;
playlist_range_from = start_from;
@@ -149,6 +153,7 @@ function displayPlaylist(data) {
}
displayActiveItem(data.current_index);
updatePlayerInfo(items[data.current_index]);
bindPlaylistEvent();
playlist_table.animate({ opacity: 1 }, 200);
});
@@ -221,17 +226,22 @@ function checkForPlaylistUpdate() {
updatePlaylist();
}
if (data.current_index !== playlist_current_index) {
if ((data.current_index > playlist_range_to || data.current_index < playlist_range_from)
&& data.current_index !== -1) {
playlist_range_from = 0;
playlist_range_to = 0;
updatePlaylist();
} else {
playlist_current_index = data.current_index;
displayActiveItem(data.current_index);
if (data.current_index !== -1) {
if ((data.current_index > playlist_range_to || data.current_index < playlist_range_from)) {
playlist_range_from = 0;
playlist_range_to = 0;
updatePlaylist();
} else {
playlist_current_index = data.current_index;
updatePlayerInfo(playlist_items[data.current_index]);
displayActiveItem(data.current_index);
}
}
}
updateControls(data.empty, data.play, data.mode, data.volume);
if (!data.empty){
updatePlayerPlayhead(data.playhead);
}
}
}
});
@@ -255,6 +265,7 @@ function bindPlaylistEvent() {
}
function updateControls(empty, play, mode, volume) {
updatePlayerControls(play, empty);
if (empty) {
playPauseBtn.prop('disabled', true);
fastForwardBtn.prop('disabled', true);
@@ -305,9 +316,6 @@ function changePlayMode(mode) {
request('post', {action: mode});
}
// Check the version of playlist to see if update is needed.
setInterval(checkForPlaylistUpdate, 3000);
// ----------------------
// --- THEME SWITCHER ---
@@ -337,7 +345,6 @@ function setPageTheme(theme) {
document.getElementById("pagestyle").setAttribute("href", "static/css/bootstrap.darkly.min.css");
}
// ---------------------
// ------ Browser ------
// ---------------------
@@ -844,12 +851,6 @@ function uploadStart(){
}
}
function setProgressBar(bar, progress) {
let prog_str = Math.floor(progress*100).toString();
bar.setAttribute("aria-valuenow", prog_str);
bar.style.width = prog_str + "%";
}
function setUploadError(filename, error){
let file_progress_item = filesProgressItem[filename];
@@ -916,7 +917,8 @@ function uploadNextFile(){
req.upload.addEventListener("progress", function(e){
if (e.lengthComputable) {
setProgressBar(file_progress_item.progress, e.loaded / e.total);
let percent = e.loaded / e.total;
setProgressBar(file_progress_item.progress, percent, Math.floor(percent) + "%");
}
});
@@ -949,6 +951,153 @@ function uploadCancel(){
areYouSureToCancelUploading = !areYouSureToCancelUploading;
}
// ---------------------
// ------ Player ------
// ---------------------
const player = document.getElementById("playerToast");
const playerArtwork = document.getElementById("playerArtwork");
const playerArtworkIdle = document.getElementById("playerArtworkIdle");
const playerTitle = document.getElementById("playerTitle");
const playerArtist = document.getElementById("playerArtist");
const playerBar = document.getElementById("playerBar");
const playerBarBox = document.getElementById("playerBarBox");
const playerPlayBtn = document.getElementById("playerPlayBtn");
const playerPauseBtn = document.getElementById("playerPauseBtn");
const playerSkipBtn = document.getElementById("playerSkipBtn");
let currentPlayingItem = null;
function togglePlayer() {
$(player).toast("show");
}
function playerSetIdle(){
playerArtwork.style.display ='none';
playerArtworkIdle.style.display ='block';
playerTitle.textContent = '-- IDLE --';
playerArtist.textContent = '';
setProgressBar(playerBar, 0);
}
function updatePlayerInfo(item){
if (!item){
playerSetIdle();
}
playerArtwork.style.display ='block';
playerArtworkIdle.style.display ='none';
currentPlayingItem = item;
playerTitle.textContent = item.title;
playerArtist.textContent = item.artist;
playerArtwork.setAttribute("src", item.thumbnail);
if (isOverflown(playerTitle)) {
playerTitle.classList.add("scrolling")
} else {
playerTitle.classList.remove("scrolling")
}
if (isOverflown(playerArtist)) {
playerArtist.classList.add("scrolling")
} else {
playerArtist.classList.remove("scrolling")
}
}
function updatePlayerControls(play, empty) {
if (empty) {
playerSetIdle();
playerPlayBtn.setAttribute("disabled", "");
playerPauseBtn.setAttribute("disabled", "");
playerSkipBtn.setAttribute("disabled", "");
} else {
playerPlayBtn.removeAttribute("disabled");
playerPauseBtn.removeAttribute("disabled");
playerSkipBtn.removeAttribute("disabled");
}
if (play) {
playerPlayBtn.style.display ='none';
playerPauseBtn.style.display = 'block';
} else {
playerPlayBtn.style.display = 'block';
playerPauseBtn.style.display = 'none';
}
}
let playhead_timer;
let player_playhead_position;
let playhead_dragging = false;
function updatePlayerPlayhead(playhead){
if (!currentPlayingItem || playhead_dragging){
return;
}
if (currentPlayingItem.duration !== 0 || currentPlayingItem.duration < playhead){
playerBar.classList.remove("progress-bar-animated");
clearInterval(playhead_timer);
player_playhead_position = playhead;
setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));
if (playing) {
playhead_timer = setInterval(function () {
player_playhead_position += 0.1;
setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));
}, 100); // delay in milliseconds
}
} else {
if (playing) {
playerBar.classList.add("progress-bar-animated");
} else {
playerBar.classList.remove("progress-bar-animated");
}
setProgressBar(playerBar, 1);
}
}
playerBarBox.addEventListener('mousedown', function () {
if (currentPlayingItem && currentPlayingItem.duration > 0){
playerBarBox.addEventListener('mousemove', playheadDragged);
clearInterval(playhead_timer);
playhead_dragging = true;
}
});
playerBarBox.addEventListener('mouseup', function (event) {
playerBarBox.removeEventListener('mousemove', playheadDragged);
let percent = event.offsetX / playerBarBox.clientWidth;
request('post', {move_playhead: percent * currentPlayingItem.duration});
playhead_dragging = false;
});
function playheadDragged(event){
let percent = event.offsetX / playerBarBox.clientWidth;
setProgressBar(playerBar, percent, secondsToStr(percent * currentPlayingItem.duration));
}
// ---------------------
// ------- Util -------
// ---------------------
function isOverflown(element) {
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}
function setProgressBar(bar, progress, text="") {
let prog_str = (progress*100).toString();
bar.setAttribute("aria-valuenow", prog_str);
bar.style.width = prog_str + "%";
bar.textContent = text;
}
function secondsToStr(seconds) {
seconds = Math.floor(seconds);
let mins = Math.floor(seconds / 60);
let secs = seconds % 60;
return ("00" + mins).slice(-2) + ":" + ("00" + secs).slice(-2);
}
themeInit();
updateResults();
$(document).ready(updatePlaylist);
$(document).ready(updatePlaylist);
// Check the version of playlist to see if update is needed.
setInterval(checkForPlaylistUpdate, 3000);
+46 -1
View File
@@ -410,10 +410,55 @@
</div>
</div>
<div class="floating-button" onclick="switchTheme()">
<div class="floating-button" style="bottom: 120px;" onclick="togglePlayer()">
<i class="fas fa-play" aria-hidden="true"></i>
</div>
<div class="floating-button" style="bottom: 50px;" onclick="switchTheme()">
<i class="fas fa-lightbulb" aria-hidden="true"></i>
</div>
<div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<i class="fas fa-play-circle mr-2 text-primary"></i>
<strong class="mr-auto">Now Playing...</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="playerContainer">
<img id="playerArtworkIdle" src="static/image/empty_box.svg" />
<img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;"/>
<div id="playerInfo">
<div id="playerActionBox">
<button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
onclick="request('post', {action: 'resume'})">
<i class="fas fa-play"></i>
</button>
<button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
onclick="request('post', {action: 'pause'})">
<i class="fas fa-pause"></i>
</button>
<button id="playerSkipBtn" class="btn btn-primary btn-sm">
<i class="fas fa-fast-forward"></i>
</button>
</div>
<div style="overflow: hidden; max-width: 320px;">
<strong id="playerTitle">Song Title</strong>
</div>
<span id="playerArtist">Artist</span>
<div id="playerBarBox" class="progress">
<div id="playerBar" class="progress-bar" role="progressbar" aria-valuenow="50"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</div>
</div>
<div id="footer" style="height:50px; width: 100%; margin-top: 100px;"></div>
<form id="download-form" action="download" method="GET" target="_blank">
<input hidden type="text" name="id" value="">
<input hidden type="text" name="type" value="">
+16 -1
View File
@@ -337,6 +337,22 @@ def youtube_search(query):
log.error("util: youtube query failed with error:\n %s" % error_traceback)
return False
def get_media_duration(path):
command = ("ffprobe", "-v", "quiet", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", path)
process = sp.Popen(command, stdout=sp.PIPE, stderr=sp.PIPE)
stdout, stderr = process.communicate()
try:
if not stderr:
return float(stdout)
else:
return 0
except ValueError:
return 0
class LoggerIOWrapper(io.TextIOWrapper):
def __init__(self, logger: logging.Logger, logging_level, fallback_io_buffer):
super().__init__(fallback_io_buffer, write_through=True)
@@ -351,4 +367,3 @@ class LoggerIOWrapper(io.TextIOWrapper):
else:
self.logger.log(self.logging_level, text.rstrip())
super().write(text + "\n")