good progress
This commit is contained in:
parent
90ea2174db
commit
fb7ade79cb
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
Populate player name (from most used one) after database is fully populated:
|
||||||
|
```SQL
|
||||||
|
UPDATE `player` SET `name`=(
|
||||||
|
SELECT `name`
|
||||||
|
FROM `player_names`
|
||||||
|
WHERE `player_names`.guid = `player`.guid
|
||||||
|
GROUP BY guid, name
|
||||||
|
ORDER BY MAX(`time`) DESC LIMIT 1
|
||||||
|
);
|
||||||
|
```
|
@ -1,5 +1,5 @@
|
|||||||
from contextlib import nullcontext
|
from contextlib import nullcontext
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from flask import Flask, g, has_app_context, request
|
from flask import Flask, g, has_app_context, request
|
||||||
from .extensions import default_config
|
from .extensions import default_config
|
||||||
@ -17,6 +17,21 @@ def register_jinja2_filters(app):
|
|||||||
def to_json(value):
|
def to_json(value):
|
||||||
return orjson.dumps(value).decode('utf-8')
|
return orjson.dumps(value).decode('utf-8')
|
||||||
|
|
||||||
|
@app.template_filter('to_duration')
|
||||||
|
def to_duration(seconds):
|
||||||
|
out = ''
|
||||||
|
(days, remainder) = divmod(seconds, 86400)
|
||||||
|
(hours, remainder) = divmod(remainder, 3600)
|
||||||
|
(minutes, seconds) = divmod(remainder, 60)
|
||||||
|
if days:
|
||||||
|
out += f'{days:.0f}d '
|
||||||
|
if hours:
|
||||||
|
out += f'{hours:.0f}h '
|
||||||
|
if minutes:
|
||||||
|
out += f'{minutes:.0f}m '
|
||||||
|
out += f'{seconds:.0f}s'
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def register_blueprints(app):
|
def register_blueprints(app):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
@ -44,15 +44,19 @@ class Player(db.Model):
|
|||||||
|
|
||||||
|
|
||||||
class PlayerNames(db.Model):
|
class PlayerNames(db.Model):
|
||||||
guid = db.Column(db.String(32), primary_key=True)
|
guid = db.Column(db.String(32), db.ForeignKey('player.guid'), primary_key=True)
|
||||||
name = db.Column(db.String(32), primary_key=True)
|
name = db.Column(db.String(32), primary_key=True)
|
||||||
time = db.Column(db.Float)
|
time = db.Column(db.Float)
|
||||||
|
|
||||||
|
player = db.relationship('Player', backref='names', foreign_keys=[guid])
|
||||||
|
|
||||||
|
|
||||||
class PlayerSprays(db.Model):
|
class PlayerSprays(db.Model):
|
||||||
guid = db.Column(db.String(32), primary_key=True)
|
guid = db.Column(db.String(32), db.ForeignKey('player.guid'), primary_key=True)
|
||||||
spray = db.Column(db.String(32), primary_key=True)
|
spray = db.Column(db.String(32), primary_key=True)
|
||||||
|
|
||||||
|
player = db.relationship('Player', backref='sprays', foreign_keys=[guid])
|
||||||
|
|
||||||
|
|
||||||
class PlayerSession(db.Model):
|
class PlayerSession(db.Model):
|
||||||
player_guid = db.Column(db.String(32), db.ForeignKey('player.guid'), primary_key=True)
|
player_guid = db.Column(db.String(32), db.ForeignKey('player.guid'), primary_key=True)
|
||||||
@ -63,6 +67,9 @@ class PlayerSession(db.Model):
|
|||||||
kills = db.Column(db.Integer)
|
kills = db.Column(db.Integer)
|
||||||
voicetime = db.Column(db.Float)
|
voicetime = db.Column(db.Float)
|
||||||
|
|
||||||
|
session = db.relationship('Session', foreign_keys=[session_id])
|
||||||
|
player = db.relationship('Player', backref='sessions', foreign_keys=[player_guid])
|
||||||
|
|
||||||
|
|
||||||
class Chat(db.Model):
|
class Chat(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
|
main { display: flex; flex-wrap: nowrap; height: 100vh; height: -webkit-fill-available; max-height: 100vh; overflow-x: auto; overflow-y: hidden; }
|
||||||
|
|
||||||
.btn-group-xs > .btn, .btn-xs { padding: .25rem .4rem; font-size: .875rem; line-height: .5; border-radius: .2rem; }
|
.btn-group-xs > .btn, .btn-xs { padding: .25rem .4rem; font-size: .875rem; line-height: .5; border-radius: .2rem; }
|
||||||
|
|
||||||
.playlist { margin: 2em 0; }
|
|
||||||
|
|
||||||
.playlist .playlist-time-scale { height: 30px; }
|
.playlist .playlist-time-scale { height: 30px; }
|
||||||
|
|
||||||
.playlist .playlist-tracks { background: #e0eff1; }
|
.playlist .playlist-tracks { background: #e0eff1; }
|
||||||
@ -31,10 +30,12 @@
|
|||||||
|
|
||||||
.playlist .controls { background: white; text-align: center; border: 1px solid black; border-radius: 0.2rem; }
|
.playlist .controls { background: white; text-align: center; border: 1px solid black; border-radius: 0.2rem; }
|
||||||
|
|
||||||
.playlist .controls .track-header { overflow: hidden; color: black; height: 26px; display: flex; align-items: center; justify-content: space-between; padding: 0 0.2rem; font-size: 0.65rem; margin-bottom: -10px; }
|
.playlist .controls .track-header { overflow: hidden; color: black; height: 18px; display: flex; align-items: center; justify-content: space-between; padding: 0 0.2rem; font-size: 0.65rem; margin-bottom: -10px; }
|
||||||
|
|
||||||
.playlist .controls .track-header button { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; }
|
.playlist .controls .track-header button { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; }
|
||||||
|
|
||||||
|
.playlist .controls .track-header span { margin-top: -8px; }
|
||||||
|
|
||||||
.playlist .controls input[type="range"] { display: inline-block; width: 90%; }
|
.playlist .controls input[type="range"] { display: inline-block; width: 90%; }
|
||||||
|
|
||||||
.playlist .controls .user-info { word-break: break-word; color: black; align-items: center; justify-content: space-between; padding: 0 0.2rem; font-size: 0.65rem; margin-top: -5px; }
|
.playlist .controls .info { word-break: break-word; color: black; align-items: center; justify-content: space-between; padding: 0 0.2rem; font-size: 0.65rem; margin-top: -10px; }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
var playlist = WaveformPlaylist.init({
|
var playlist = WaveformPlaylist.init({
|
||||||
container: document.getElementById("playlist"),
|
container: document.getElementById("playlist"),
|
||||||
timescale: true,
|
timescale: true,
|
||||||
state: 'cursor',
|
state: 'select',
|
||||||
samplesPerPixel: 16384,
|
samplesPerPixel: 16384,
|
||||||
zoomLevels: [2048, 4096, 8192, 16384],
|
zoomLevels: [2048, 4096, 8192, 16384],
|
||||||
|
|
||||||
@ -12,23 +12,58 @@ var playlist = WaveformPlaylist.init({
|
|||||||
stereoPan: false,
|
stereoPan: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
waveHeight: 96,
|
waveHeight: 80,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onFinishedLoading() {
|
||||||
|
//initialize the WAV exporter.
|
||||||
|
playlist.initExporter();
|
||||||
|
|
||||||
|
const tracks = playlist.tracks;
|
||||||
|
for (var i = 0; i < tracks.length; i++) {
|
||||||
|
playlist.collapseTrack(tracks[i], {collapsed: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlight = window.location.hash.split('#').filter(Boolean);
|
||||||
|
for(var i = 0; i < highlight.length; i++) {
|
||||||
|
const guid = highlight[i];
|
||||||
|
|
||||||
|
for (var j = 0; j < tracks.length; j++) {
|
||||||
|
if (tracks[j].name == guid) {
|
||||||
|
tracks[j].setWaveOutlineColor('#d1e7dd');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < g_chats.length; j++) {
|
||||||
|
if (g_chats[j].player_guid == guid) {
|
||||||
|
chatRows[j].classList.add("table-active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.drawRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseTrack(guid, collapse) {
|
||||||
|
const tracks = playlist.tracks;
|
||||||
|
for (var i = 0; i < tracks.length; i++) {
|
||||||
|
if (guid === null || tracks[i].name == guid) {
|
||||||
|
playlist.collapseTrack(tracks[i], {collapsed: collapse});
|
||||||
|
if (guid != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateTrackInfo(guid, info) {
|
function updateTrackInfo(guid, info) {
|
||||||
var tracks = $(".playlist-tracks").children();
|
const tracks = playlist.tracks;
|
||||||
for (var i = 0; i < tracks.length; i++) {
|
for (var i = 0; i < tracks.length; i++) {
|
||||||
var track = tracks[i].firstChild;
|
if (guid === null || tracks[i].name == guid) {
|
||||||
var trackGuid = track.firstChild.getElementsByTagName('span')[0].innerText;
|
tracks[i].setInfo(info);
|
||||||
var userInfoElem = track.lastChild;
|
if (guid != null) {
|
||||||
if (userInfoElem.className != 'user-info') {
|
break;
|
||||||
userInfoElem = document.createElement('label');
|
}
|
||||||
userInfoElem.className = 'user-info';
|
|
||||||
track.appendChild(userInfoElem);
|
|
||||||
}
|
|
||||||
if (guid === null || trackGuid == guid) {
|
|
||||||
userInfoElem.innerText = info;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,25 +91,144 @@ function clockFormat(seconds, decimals) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lastChunkIdx = 0;
|
||||||
|
var lastUpdateTime = 0;
|
||||||
function updateTime(time) {
|
function updateTime(time) {
|
||||||
$time.html(clockFormat(time, 3));
|
$time.html(clockFormat(time, 3));
|
||||||
audioPos = time;
|
audioPos = time;
|
||||||
|
|
||||||
|
if (time < lastUpdateTime) {
|
||||||
|
lastChunkIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
var tick = time / g_session.tickinterval;
|
var tick = time / g_session.tickinterval;
|
||||||
var silenceTicks = 0;
|
var silenceTicks = 0;
|
||||||
for (var i = 0; i < g_session.silence_chunks.length; i++) {
|
for (; lastChunkIdx < g_session.silence_chunks.length; lastChunkIdx++) {
|
||||||
var chunk = g_session.silence_chunks[i];
|
var chunk = g_session.silence_chunks[lastChunkIdx];
|
||||||
if (tick > chunk[0]) {
|
if (tick > chunk[0]) {
|
||||||
silenceTicks += chunk[1];
|
silenceTicks = chunk[1];
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var tickedTime = (tick + silenceTicks) * g_session.tickinterval;
|
|
||||||
|
tick += silenceTicks;
|
||||||
|
var tickedTime = tick * g_session.tickinterval;
|
||||||
$time_.html(clockFormat(tickedTime, 3));
|
$time_.html(clockFormat(tickedTime, 3));
|
||||||
|
|
||||||
|
lastUpdateTime = time;
|
||||||
|
if (lastChunkIdx > 0) {
|
||||||
|
lastChunkIdx -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
onTick(tick, tickedTime);
|
||||||
|
}
|
||||||
|
updateTime(audioPos);
|
||||||
|
|
||||||
|
function onEvent(idx, event) {
|
||||||
|
var update = 0;
|
||||||
|
if (event.event == "player_connect") {
|
||||||
|
collapseTrack(event.player_guid, false);
|
||||||
|
updateTrackInfo(event.player_guid, event.data.name);
|
||||||
|
update += 1;
|
||||||
|
}
|
||||||
|
else if (event.event == "player_disconnect") {
|
||||||
|
collapseTrack(event.player_guid, true);
|
||||||
|
update += 1;
|
||||||
|
}
|
||||||
|
else if (event.event == "player_changename") {
|
||||||
|
updateTrackInfo(event.player_guid, event.data.newname);
|
||||||
|
update += 1;
|
||||||
|
}
|
||||||
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTime(audioPos);
|
var chatBox = $("div#chat");
|
||||||
|
var chatRows = $("div#chat>table>tbody").children();
|
||||||
|
var lastPrimaryRow = undefined;
|
||||||
|
function onChat(idx, chat) {
|
||||||
|
if (idx == lastPrimaryRow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPrimaryRow != undefined) {
|
||||||
|
chatRows[lastPrimaryRow].classList.remove("table-primary");
|
||||||
|
}
|
||||||
|
|
||||||
|
chatRows[idx].classList.add("table-primary");
|
||||||
|
if (autoScrollChat) {
|
||||||
|
chatRows[idx].scrollIntoViewIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPrimaryRow = idx;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastTick = undefined;
|
||||||
|
var lastChatIdx = 0;
|
||||||
|
var lastEventIdx = 0;
|
||||||
|
function onTick(tick, time) {
|
||||||
|
var update = 0;
|
||||||
|
if (tick == lastTick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tick < lastTick) {
|
||||||
|
lastChatIdx = 0;
|
||||||
|
lastEventIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; lastEventIdx < g_events.length; lastEventIdx++) {
|
||||||
|
const event = g_events[lastEventIdx];
|
||||||
|
if (tick > event.tick) {
|
||||||
|
update += onEvent(lastEventIdx, event);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastEventIdx > 0) {
|
||||||
|
lastEventIdx -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; lastChatIdx < g_chats.length; lastChatIdx++) {
|
||||||
|
const chat = g_chats[lastChatIdx];
|
||||||
|
if (tick < chat.tick) {
|
||||||
|
if (lastChatIdx > 0) {
|
||||||
|
lastChatIdx -= 1;
|
||||||
|
}
|
||||||
|
update += onChat(lastChatIdx, chat);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTick = tick;
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
playlist.drawRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameTimeToAudio(tick) {
|
||||||
|
tick += 1;
|
||||||
|
var silenceTicks = 0;
|
||||||
|
for (var i = 0; i < g_session.silence_chunks.length; i++) {
|
||||||
|
const chunk = g_session.silence_chunks[i];
|
||||||
|
if ((tick - chunk[1]) > chunk[0]) {
|
||||||
|
silenceTicks = chunk[1];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (tick - silenceTicks) * g_session.tickinterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpToGameTick(tick) {
|
||||||
|
var audioTime = gameTimeToAudio(tick);
|
||||||
|
playlist.seek(audioTime);
|
||||||
|
playlist.drawRequest();
|
||||||
|
updateTime(audioTime);
|
||||||
|
}
|
||||||
|
|
||||||
$container.on("click", ".btn-play", function() {
|
$container.on("click", ".btn-play", function() {
|
||||||
ee.emit("play");
|
ee.emit("play");
|
||||||
@ -100,7 +254,7 @@ $container.on("click", ".btn-fast-forward", function() {
|
|||||||
ee.emit("fastforward");
|
ee.emit("fastforward");
|
||||||
});
|
});
|
||||||
|
|
||||||
//zoom buttons
|
// zoom buttons
|
||||||
$container.on("click", ".btn-zoom-in", function() {
|
$container.on("click", ".btn-zoom-in", function() {
|
||||||
ee.emit("zoomin");
|
ee.emit("zoomin");
|
||||||
});
|
});
|
||||||
@ -109,21 +263,102 @@ $container.on("click", ".btn-zoom-out", function() {
|
|||||||
ee.emit("zoomout");
|
ee.emit("zoomout");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// download
|
||||||
|
var downloadUrl = undefined;
|
||||||
|
var downloadName = undefined;
|
||||||
|
$container.on("click", ".btn-download", function () {
|
||||||
|
if (downloadName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadName = g_session.demoname;
|
||||||
|
if (playlist.isSegmentSelection()) {
|
||||||
|
const segment = playlist.getTimeSelection();
|
||||||
|
downloadName += "-" + clockFormat(segment.start).replaceAll(':', '-') + "_" + clockFormat(segment.end).replaceAll(':', '-');
|
||||||
|
}
|
||||||
|
downloadName += ".wav";
|
||||||
|
|
||||||
|
ee.emit('startaudiorendering', 'wav');
|
||||||
|
});
|
||||||
|
|
||||||
|
ee.on('audiorenderingfinished', function (type, data) {
|
||||||
|
if (type != 'wav') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (downloadUrl) {
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadUrl = window.URL.createObjectURL(data);
|
||||||
|
|
||||||
|
const tempLink = document.createElement('a');
|
||||||
|
tempLink.style.display = 'none';
|
||||||
|
tempLink.href = downloadUrl;
|
||||||
|
tempLink.setAttribute('download', downloadName);
|
||||||
|
document.body.appendChild(tempLink);
|
||||||
|
tempLink.click();
|
||||||
|
document.body.removeChild(tempLink);
|
||||||
|
|
||||||
|
downloadName = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
$container.on("input change", ".master-gain", function(e){
|
$container.on("input change", ".master-gain", function(e){
|
||||||
ee.emit("mastervolumechange", e.target.value);
|
ee.emit("mastervolumechange", e.target.value);
|
||||||
});
|
});
|
||||||
$container.find(".master-gain").change();
|
$container.find(".master-gain").change();
|
||||||
|
|
||||||
$container.on("change", ".automatic-scroll", function(e){
|
var autoScrollVoice = false;
|
||||||
ee.emit("automaticscroll", $(e.target).is(':checked'));
|
$container.on("change", "#autoscroll_voice", function(e){
|
||||||
|
autoScrollVoice = $(e.target).is(':checked');
|
||||||
|
ee.emit("automaticscroll", autoScrollVoice);
|
||||||
});
|
});
|
||||||
$container.find(".automatic-scroll").change();
|
$container.find("#autoscroll_voice").change();
|
||||||
|
|
||||||
|
var autoScrollChat = false;
|
||||||
|
$container.on("change", "#autoscroll_chat", function(e){
|
||||||
|
autoScrollChat = $(e.target).is(':checked');
|
||||||
|
});
|
||||||
|
$container.find("#autoscroll_chat").change();
|
||||||
|
|
||||||
ee.on("timeupdate", updateTime);
|
ee.on("timeupdate", updateTime);
|
||||||
|
|
||||||
|
|
||||||
function onFinishedLoading() {
|
function getParent(el) {
|
||||||
updateTrackInfo(null, "name");
|
var parent = el.parentNode;
|
||||||
|
|
||||||
|
if (parent === document) {
|
||||||
|
return document;
|
||||||
|
} else if (parent.offsetHeight < parent.scrollHeight || parent.offsetWidth < parent.scrollWidth) {
|
||||||
|
return parent;
|
||||||
|
} else {
|
||||||
|
return getParent(parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (!Element.prototype.scrollIntoViewIfNeeded) {
|
||||||
|
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
|
||||||
|
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
|
||||||
|
|
||||||
|
var parent = getParent(this),
|
||||||
|
parentComputedStyle = window.getComputedStyle(parent, null),
|
||||||
|
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
|
||||||
|
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
|
||||||
|
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
|
||||||
|
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
|
||||||
|
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
|
||||||
|
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
|
||||||
|
alignWithTop = overTop && !overBottom;
|
||||||
|
|
||||||
|
if ((overTop || overBottom) && centerIfNeeded) {
|
||||||
|
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((overLeft || overRight) && centerIfNeeded) {
|
||||||
|
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
|
||||||
|
this.scrollIntoView(alignWithTop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -106,7 +106,8 @@ var WaveformPlaylist =
|
|||||||
volume: true,
|
volume: true,
|
||||||
stereoPan: true,
|
stereoPan: true,
|
||||||
collapse: true,
|
collapse: true,
|
||||||
remove: true
|
remove: true,
|
||||||
|
info: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
@ -3465,6 +3466,7 @@ var WaveformPlaylist =
|
|||||||
var tracks = audioBuffers.map(function (audioBuffer, index) {
|
var tracks = audioBuffers.map(function (audioBuffer, index) {
|
||||||
var info = trackList[index];
|
var info = trackList[index];
|
||||||
var name = info.name || 'Untitled';
|
var name = info.name || 'Untitled';
|
||||||
|
var infostr = info.info || undefined;
|
||||||
var start = info.start || 0;
|
var start = info.start || 0;
|
||||||
var states = info.states || {};
|
var states = info.states || {};
|
||||||
var fadeIn = info.fadeIn;
|
var fadeIn = info.fadeIn;
|
||||||
@ -3487,6 +3489,7 @@ var WaveformPlaylist =
|
|||||||
track.src = info.src;
|
track.src = info.src;
|
||||||
track.setBuffer(audioBuffer);
|
track.setBuffer(audioBuffer);
|
||||||
track.setName(name);
|
track.setName(name);
|
||||||
|
track.setInfo(infostr);
|
||||||
track.setEventEmitter(_this3.ee);
|
track.setEventEmitter(_this3.ee);
|
||||||
track.setEnabledStates(states);
|
track.setEnabledStates(states);
|
||||||
track.setCues(cueIn, cueOut);
|
track.setCues(cueIn, cueOut);
|
||||||
@ -3586,15 +3589,24 @@ var WaveformPlaylist =
|
|||||||
if (this.isRendering) {
|
if (this.isRendering) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isRendering = true;
|
this.isRendering = true;
|
||||||
this.offlineAudioContext = new OfflineAudioContext(2, 44100 * this.duration, 44100);
|
|
||||||
|
|
||||||
|
var duration = this.duration;
|
||||||
|
var startTime = 0;
|
||||||
|
var endTime = 0;
|
||||||
|
if (this.isSegmentSelection()) {
|
||||||
|
var segment = this.getTimeSelection();
|
||||||
|
startTime = segment.start;
|
||||||
|
endTime = segment.end;
|
||||||
|
duration = endTime - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.offlineAudioContext = new OfflineAudioContext(1, 44100 * duration, 44100);
|
||||||
var currentTime = this.offlineAudioContext.currentTime;
|
var currentTime = this.offlineAudioContext.currentTime;
|
||||||
|
|
||||||
this.tracks.forEach(function (track) {
|
this.tracks.forEach(function (track) {
|
||||||
track.setOfflinePlayout(new _Playout2.default(_this4.offlineAudioContext, track.buffer));
|
track.setOfflinePlayout(new _Playout2.default(_this4.offlineAudioContext, track.buffer));
|
||||||
track.schedulePlay(currentTime, 0, 0, {
|
track.schedulePlay(currentTime, startTime, endTime, {
|
||||||
shouldPlay: _this4.shouldTrackPlay(track),
|
shouldPlay: _this4.shouldTrackPlay(track),
|
||||||
masterGain: 1,
|
masterGain: 1,
|
||||||
isOffline: true
|
isOffline: true
|
||||||
@ -3615,7 +3627,8 @@ var WaveformPlaylist =
|
|||||||
_this4.exportWorker.postMessage({
|
_this4.exportWorker.postMessage({
|
||||||
command: 'init',
|
command: 'init',
|
||||||
config: {
|
config: {
|
||||||
sampleRate: 44100
|
sampleRate: 44100,
|
||||||
|
stereo: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -3633,7 +3646,7 @@ var WaveformPlaylist =
|
|||||||
// send the channel data from our buffer to the worker
|
// send the channel data from our buffer to the worker
|
||||||
_this4.exportWorker.postMessage({
|
_this4.exportWorker.postMessage({
|
||||||
command: 'record',
|
command: 'record',
|
||||||
buffer: [audioBuffer.getChannelData(0), audioBuffer.getChannelData(1)]
|
buffer: [audioBuffer.getChannelData(0)]
|
||||||
});
|
});
|
||||||
|
|
||||||
// ask the worker for a WAV
|
// ask the worker for a WAV
|
||||||
@ -5905,7 +5918,7 @@ var WaveformPlaylist =
|
|||||||
|
|
||||||
if (audioData.byteLength > 16) {
|
if (audioData.byteLength > 16) {
|
||||||
var view = new DataView(audioData);
|
var view = new DataView(audioData);
|
||||||
var wanted = "DEMOPUSHEADER_V1";
|
var wanted = "DEMOPUSHEADER_V2";
|
||||||
var success = true;
|
var success = true;
|
||||||
for (var i = 0, n = 16; i < n; i++) {
|
for (var i = 0, n = 16; i < n; i++) {
|
||||||
var c = view.getUint8(i);
|
var c = view.getUint8(i);
|
||||||
@ -5942,74 +5955,54 @@ var WaveformPlaylist =
|
|||||||
var _this2 = this;
|
var _this2 = this;
|
||||||
|
|
||||||
this.setStateChange(STATE_DECODING);
|
this.setStateChange(STATE_DECODING);
|
||||||
|
var promises = [];
|
||||||
var parsed = [];
|
|
||||||
var sampleRate = 0;
|
|
||||||
var numSamples = 0;
|
|
||||||
var channels = 1;
|
|
||||||
|
|
||||||
var view = new DataView(demopusData);
|
var view = new DataView(demopusData);
|
||||||
var ofs = 16; // skip header
|
var ofs = 16; // skip header
|
||||||
|
|
||||||
|
var channels = 1;
|
||||||
|
var sampleRate = view.getUint32(ofs, true);
|
||||||
|
ofs += 4;
|
||||||
|
var numSamples = Number(view.getBigUint64(ofs, true));
|
||||||
|
ofs += 8;
|
||||||
|
|
||||||
|
// output sample rate != input sample rate
|
||||||
|
numSamples *= this.ac.sampleRate / sampleRate;
|
||||||
|
var audioBuffer = this.ac.createBuffer(channels, numSamples, this.ac.sampleRate);
|
||||||
|
|
||||||
while (ofs < demopusData.byteLength) {
|
while (ofs < demopusData.byteLength) {
|
||||||
var header = view.getUint8(ofs);
|
var samplesOfs = Number(view.getBigUint64(ofs, true));
|
||||||
ofs += 1;
|
ofs += 8;
|
||||||
|
samplesOfs *= this.ac.sampleRate / sampleRate;
|
||||||
|
|
||||||
if (header == 0x02) {
|
if (ofs >= demopusData.byteLength) {
|
||||||
// opus
|
|
||||||
var dataLen = Number(view.getBigUint64(ofs, true));
|
|
||||||
ofs += 8;
|
|
||||||
var opusData = demopusData.slice(ofs, ofs + dataLen);
|
|
||||||
ofs += dataLen;
|
|
||||||
|
|
||||||
var promise = this.ac.decodeAudioData(opusData, function (audioBuffer) {
|
|
||||||
return audioBuffer;
|
|
||||||
}, function (err) {
|
|
||||||
if (err === null) {
|
|
||||||
// Safari issues with null error
|
|
||||||
return Error('MediaDecodeAudioDataUnknownContentType');
|
|
||||||
} else {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
parsed.push(promise);
|
|
||||||
} else if (header == 0x03) {
|
|
||||||
// silence
|
|
||||||
var samples = Number(view.getBigUint64(ofs, true));
|
|
||||||
ofs += 8;
|
|
||||||
parsed.push(samples);
|
|
||||||
} else if (header == 0x01) {
|
|
||||||
// info
|
|
||||||
sampleRate = view.getUint32(ofs, true);
|
|
||||||
ofs += 4;
|
|
||||||
numSamples = Number(view.getBigUint64(ofs, true));
|
|
||||||
ofs += 8;
|
|
||||||
} else if (header == 0x04) {
|
|
||||||
// done
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dataLen = view.getUint32(ofs, true);
|
||||||
|
ofs += 4;
|
||||||
|
|
||||||
|
var opusData = demopusData.slice(ofs, ofs + dataLen);
|
||||||
|
ofs += dataLen;
|
||||||
|
|
||||||
|
var promise = this.ac.decodeAudioData(opusData, function (decoded) {
|
||||||
|
var buf = decoded.getChannelData(0);
|
||||||
|
audioBuffer.copyToChannel(buf, 0, this);
|
||||||
|
return decoded.length;
|
||||||
|
}.bind(samplesOfs), function (err) {
|
||||||
|
if (err === null) {
|
||||||
|
// Safari issues with null error
|
||||||
|
return Error('MediaDecodeAudioDataUnknownContentType');
|
||||||
|
} else {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
promises.push(promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
// output sample rate != input sample rate
|
Promise.all(promises).then(function (result) {
|
||||||
numSamples *= _this2.ac.sampleRate / sampleRate;
|
|
||||||
var audioBuffer = _this2.ac.createBuffer(channels, numSamples, _this2.ac.sampleRate);
|
|
||||||
|
|
||||||
return Promise.all(parsed).then(function (result) {
|
|
||||||
var curSamples = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < result.length; i++) {
|
|
||||||
var elem = result[i];
|
|
||||||
if (typeof elem == "number") {
|
|
||||||
curSamples += elem * (_this2.ac.sampleRate / sampleRate);
|
|
||||||
} else {
|
|
||||||
var buf = elem.getChannelData(0);
|
|
||||||
audioBuffer.copyToChannel(buf, 0, curSamples);
|
|
||||||
curSamples += elem.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_this2.setStateChange(STATE_FINISHED);
|
_this2.setStateChange(STATE_FINISHED);
|
||||||
resolve(audioBuffer);
|
resolve(audioBuffer);
|
||||||
});
|
});
|
||||||
@ -6505,6 +6498,7 @@ var WaveformPlaylist =
|
|||||||
_classCallCheck(this, _class);
|
_classCallCheck(this, _class);
|
||||||
|
|
||||||
this.name = 'Untitled';
|
this.name = 'Untitled';
|
||||||
|
this.info = undefined;
|
||||||
this.customClass = undefined;
|
this.customClass = undefined;
|
||||||
this.waveOutlineColor = undefined;
|
this.waveOutlineColor = undefined;
|
||||||
this.gain = 1;
|
this.gain = 1;
|
||||||
@ -6532,6 +6526,11 @@ var WaveformPlaylist =
|
|||||||
value: function setName(name) {
|
value: function setName(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'setInfo',
|
||||||
|
value: function setInfo(info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'setCustomClass',
|
key: 'setCustomClass',
|
||||||
value: function setCustomClass(className) {
|
value: function setCustomClass(className) {
|
||||||
@ -6980,6 +6979,10 @@ var WaveformPlaylist =
|
|||||||
}
|
}
|
||||||
})]));
|
})]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widgets.info) {
|
||||||
|
controls.push((0, _h2.default)('label.info', [this.info]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (0, _h2.default)('div.controls', {
|
return (0, _h2.default)('div.controls', {
|
||||||
@ -7134,6 +7137,7 @@ var WaveformPlaylist =
|
|||||||
start: this.startTime,
|
start: this.startTime,
|
||||||
end: this.endTime,
|
end: this.endTime,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
info: this.info,
|
||||||
customClass: this.customClass,
|
customClass: this.customClass,
|
||||||
cuein: this.cueIn,
|
cuein: this.cueIn,
|
||||||
cueout: this.cueOut,
|
cueout: this.cueOut,
|
||||||
@ -10408,14 +10412,18 @@ var WaveformPlaylist =
|
|||||||
var recBuffersL = [];
|
var recBuffersL = [];
|
||||||
var recBuffersR = [];
|
var recBuffersR = [];
|
||||||
var sampleRate = void 0;
|
var sampleRate = void 0;
|
||||||
|
var stereo = void 0;
|
||||||
|
|
||||||
function init(config) {
|
function init(config) {
|
||||||
sampleRate = config.sampleRate;
|
sampleRate = config.sampleRate;
|
||||||
|
stereo = config.stereo;
|
||||||
}
|
}
|
||||||
|
|
||||||
function record(inputBuffer) {
|
function record(inputBuffer) {
|
||||||
recBuffersL.push(inputBuffer[0]);
|
recBuffersL.push(inputBuffer[0]);
|
||||||
recBuffersR.push(inputBuffer[1]);
|
if (stereo) {
|
||||||
|
recBuffersR.push(inputBuffer[1]);
|
||||||
|
}
|
||||||
recLength += inputBuffer[0].length;
|
recLength += inputBuffer[0].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10434,15 +10442,14 @@ var WaveformPlaylist =
|
|||||||
}
|
}
|
||||||
|
|
||||||
function encodeWAV(samples) {
|
function encodeWAV(samples) {
|
||||||
var mono = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
var numChannels = stereo ? 2 : 1;
|
||||||
|
|
||||||
var buffer = new ArrayBuffer(44 + samples.length * 2);
|
var buffer = new ArrayBuffer(44 + samples.length * 2);
|
||||||
var view = new DataView(buffer);
|
var view = new DataView(buffer);
|
||||||
|
|
||||||
/* RIFF identifier */
|
/* RIFF identifier */
|
||||||
writeString(view, 0, 'RIFF');
|
writeString(view, 0, 'RIFF');
|
||||||
/* file length */
|
/* file length */
|
||||||
view.setUint32(4, 32 + samples.length * 2, true);
|
view.setUint32(4, 36 + samples.length * 2, true);
|
||||||
/* RIFF type */
|
/* RIFF type */
|
||||||
writeString(view, 8, 'WAVE');
|
writeString(view, 8, 'WAVE');
|
||||||
/* format chunk identifier */
|
/* format chunk identifier */
|
||||||
@ -10452,13 +10459,13 @@ var WaveformPlaylist =
|
|||||||
/* sample format (raw) */
|
/* sample format (raw) */
|
||||||
view.setUint16(20, 1, true);
|
view.setUint16(20, 1, true);
|
||||||
/* channel count */
|
/* channel count */
|
||||||
view.setUint16(22, mono ? 1 : 2, true);
|
view.setUint16(22, numChannels, true);
|
||||||
/* sample rate */
|
/* sample rate */
|
||||||
view.setUint32(24, sampleRate, true);
|
view.setUint32(24, sampleRate, true);
|
||||||
/* byte rate (sample rate * block align) */
|
/* byte rate (sample rate * channel count * bytes per sample) */
|
||||||
view.setUint32(28, sampleRate * 4, true);
|
view.setUint32(28, sampleRate * numChannels * 2, true);
|
||||||
/* block align (channel count * bytes per sample) */
|
/* block align (channel count * bytes per sample) */
|
||||||
view.setUint16(32, 4, true);
|
view.setUint16(32, numChannels * 2, true);
|
||||||
/* bits per sample */
|
/* bits per sample */
|
||||||
view.setUint16(34, 16, true);
|
view.setUint16(34, 16, true);
|
||||||
/* data chunk identifier */
|
/* data chunk identifier */
|
||||||
@ -10500,8 +10507,11 @@ var WaveformPlaylist =
|
|||||||
|
|
||||||
function exportWAV(type) {
|
function exportWAV(type) {
|
||||||
var bufferL = mergeBuffers(recBuffersL, recLength);
|
var bufferL = mergeBuffers(recBuffersL, recLength);
|
||||||
var bufferR = mergeBuffers(recBuffersR, recLength);
|
var interleaved = bufferL;
|
||||||
var interleaved = interleave(bufferL, bufferR);
|
if (stereo) {
|
||||||
|
var bufferR = mergeBuffers(recBuffersR, recLength);
|
||||||
|
interleaved = interleave(bufferL, bufferR);
|
||||||
|
}
|
||||||
var dataview = encodeWAV(interleaved);
|
var dataview = encodeWAV(interleaved);
|
||||||
var audioBlob = new Blob([dataview], { type: type });
|
var audioBlob = new Blob([dataview], { type: type });
|
||||||
|
|
||||||
|
1
demweb/static/js/waveform-playlist.var.js.map
Normal file
1
demweb/static/js/waveform-playlist.var.js.map
Normal file
File diff suppressed because one or more lines are too long
172
demweb/templates/player.html
Normal file
172
demweb/templates/player.html
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<title>
|
||||||
|
{{ player.guid }} - {{ player.name }}
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
|
||||||
|
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.3/css/theme.bootstrap_4.min.css"
|
||||||
|
integrity="sha512-2C6AmJKgt4B+bQc08/TwUeFKkq8CsBNlTaNcNgUmsDJSU1Fg+R6azDbho+ZzuxEkJnCjLZQMozSq3y97ZmgwjA=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main class="container">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-sm table-borderless">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">guid</th>
|
||||||
|
<td>
|
||||||
|
<a href="https://steamcommunity.com/profiles/{{ player.guid }}" target="_blank">
|
||||||
|
{{ player.guid }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">name</th>
|
||||||
|
<td>{{ player.name }}</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">first_seen</th>
|
||||||
|
<td>{{ player.first_seen }}</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">last_seen</th>
|
||||||
|
<td>{{ player.last_seen }}</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">playtime</th>
|
||||||
|
<td>{{ player.playtime | to_duration }}</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">chats</th>
|
||||||
|
<td>{{ player.chats }}</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">deaths</th>
|
||||||
|
<td>{{ player.deaths }}</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">kills</th>
|
||||||
|
<td>{{ player.kills }}</td>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">voicetime</th>
|
||||||
|
<td>{{ player.voicetime | to_duration }}</td>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-striped tablesorter" id="playerNamesTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col" class="sorter-duration">Time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for name in player.names %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ name.name }}</th>
|
||||||
|
<td>{{ (name.time * 0.015) | to_duration }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-striped" id="playerSpraysTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Spray</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for spray in player.sprays %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ spray.spray }}</th>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h3>Sessions</h3>
|
||||||
|
<table class="table table-striped tablesorter" id="playerSessionsTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col" class="sorter-isoDate">Time</th>
|
||||||
|
<th scope="col" class="sorter-duration">Length</th>
|
||||||
|
<th scope="col">Map</th>
|
||||||
|
<th scope="col" class="sorter-duration">Playtime</th>
|
||||||
|
<th scope="col">Chats</th>
|
||||||
|
<th scope="col" class="sorter-duration">Voicetime</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for session in player_sessions %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<a href="{{ url_for('views.session', session_id=session.session.id) }}#{{ player.guid }}">
|
||||||
|
{{ session.session.id }}
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<td>{{ session.session.time.isoformat(' ') }}</td>
|
||||||
|
<td>{{ session.session.length | to_duration }}</td>
|
||||||
|
<td>{{ session.session.mapname }}</td>
|
||||||
|
<td>{{ session.playtime | to_duration }}</td>
|
||||||
|
<td>{{ session.chats }}</td>
|
||||||
|
<td>{{ session.voicetime | to_duration }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||||
|
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.3/js/jquery.tablesorter.min.js"
|
||||||
|
integrity="sha512-qzgd5cYSZcosqpzpn7zF2ZId8f/8CHmFKZ8j7mU4OUXTNRd5g+ZHBPsgKEwoqxCtdQvExE5LprwwPAgoicguNg=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
></script>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.3/js/parsers/parser-duration.min.js"
|
||||||
|
integrity="sha512-X7QJLLEO6yg8gSlmgRAP7Ec2qDD+ndnFcd8yagZkkN5b/7bCMbhRQdyJ4SjENUEr+4eBzgwvaFH5yR/bLJZJQA=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function() {
|
||||||
|
$("#playerNamesTable").tablesorter({
|
||||||
|
theme: "bootstrap"
|
||||||
|
});
|
||||||
|
$("#playerSessionsTable").tablesorter({
|
||||||
|
theme: "bootstrap"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -16,99 +16,113 @@
|
|||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
|
<main>
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand" href="#">Top navbar</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" aria-current="page" href="#">Home</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="#">Link</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<form class="d-flex">
|
|
||||||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="container-fluid">
|
<div class="d-flex flex-column flex-grow-1">
|
||||||
|
<div class="btn-toolbar" role="toolbar" style="min-width: max-content;">
|
||||||
<div class="btn-toolbar" role="toolbar">
|
<div class="btn-group me-2" role="group">
|
||||||
<div class="btn-group me-2" role="group">
|
<button type="button" class="btn-pause btn btn-outline-warning" title="Pause">
|
||||||
<button type="button" class="btn-pause btn btn-outline-warning" title="Pause">
|
<i class="fas fa-pause"></i>
|
||||||
<i class="fas fa-pause"></i>
|
</button>
|
||||||
</button>
|
<button type="button" class="btn-play btn btn-outline-success" title="Play">
|
||||||
<button type="button" class="btn-play btn btn-outline-success" title="Play">
|
<i class="fas fa-play"></i>
|
||||||
<i class="fas fa-play"></i>
|
</button>
|
||||||
</button>
|
<button type="button" class="btn-stop btn btn-outline-danger" title="Stop">
|
||||||
<button type="button" class="btn-stop btn btn-outline-danger" title="Stop">
|
<i class="fas fa-stop"></i>
|
||||||
<i class="fas fa-stop"></i>
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn-rewind btn btn-outline-success"
|
||||||
class="btn-rewind btn btn-outline-success"
|
title="Rewind"
|
||||||
title="Rewind"
|
>
|
||||||
>
|
<i class="fas fa-fast-backward"></i>
|
||||||
<i class="fas fa-fast-backward"></i>
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn-fast-forward btn btn-outline-success"
|
||||||
class="btn-fast-forward btn btn-outline-success"
|
title="Fast forward"
|
||||||
title="Fast forward"
|
>
|
||||||
>
|
<i class="fas fa-fast-forward"></i>
|
||||||
<i class="fas fa-fast-forward"></i>
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-group me-2" role="group">
|
|
||||||
<button type="button" title="Zoom in" class="btn-zoom-in btn btn-outline-dark">
|
|
||||||
<i class="fas fa-search-plus" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" title="Zoom out" class="btn-zoom-out btn btn-outline-dark">
|
|
||||||
<i class="fas fa-search-minus" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-group me-2">
|
|
||||||
<div style="margin: 6px">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
value="50"
|
|
||||||
class="master-gain form-range mw-50"
|
|
||||||
id="master-gain"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin: 6px">
|
<div class="btn-group me-2" role="group">
|
||||||
<span class="audio-pos" aria-label="Audio position">00:00:00.0</span>
|
<button type="button" title="Zoom in" class="btn-zoom-in btn btn-outline-dark">
|
||||||
|
<i class="fas fa-search-plus" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" title="Zoom out" class="btn-zoom-out btn btn-outline-dark">
|
||||||
|
<i class="fas fa-search-minus" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin: 6px">
|
<div class="btn-group me-2">
|
||||||
<span class="audio-pos-2" aria-label="Audio position">00:00:00.0</span>
|
<div style="margin: 6px">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
value="50"
|
||||||
|
class="master-gain form-range mw-50"
|
||||||
|
id="master-gain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check form-switch">
|
<div class="btn-group me-2">
|
||||||
<input class="form-check-input automatic-scroll" type="checkbox" id="automatic_scroll" checked>
|
<div style="margin: 6px">
|
||||||
<label class="form-check-label" for="automatic_scroll">Autoscroll</label>
|
<span class="audio-pos font-monospace" aria-label="Audio position">00:00:00.0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin: 6px">
|
||||||
|
<span class="audio-pos-2 font-monospace" aria-label="Audio position">00:00:00.0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="autoscroll_voice" checked>
|
||||||
|
<label class="form-check-label" for="autoscroll_voice">Voice</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<label class="form-check-label" for="autoscroll_chat">Chat</label>
|
||||||
|
<input class="form-check-input" type="checkbox" id="autoscroll_chat" checked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group me-2">
|
||||||
|
<button type="button" title="Download the selection as Wav file" class="btn btn-download btn-outline-primary">
|
||||||
|
<i class="fas fa-download" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-auto" id="playlist">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="playlist">
|
|
||||||
|
<div class="d-flex flex-column overflow-auto" id="chat">
|
||||||
|
<table class="table table-sm text-nowrap">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Time</th>
|
||||||
|
<th scope="col">SteamID</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for chat in chats %}
|
||||||
|
<tr>
|
||||||
|
<td onclick="jumpToGameTick({{ chat.tick }})">
|
||||||
|
{{ (chat.tick * session.tickinterval) | to_duration }}
|
||||||
|
</td>
|
||||||
|
<td>{{ chat.player_guid }}</td>
|
||||||
|
<td>{{ chat.name }}</td>
|
||||||
|
<td>{{ chat.chat }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
@ -154,7 +168,7 @@ var g_events = [
|
|||||||
playlist.load([
|
playlist.load([
|
||||||
{%- for guid, psess in player_sessions.items() %}
|
{%- for guid, psess in player_sessions.items() %}
|
||||||
{%- if psess.voicetime > 0 %}
|
{%- if psess.voicetime > 0 %}
|
||||||
{src: "/static/css-ze-parsed/{{ session.demoname }}/voice/{{ guid }}.demopus", name: "{{ guid }}"},
|
{src: "/static/css-ze-parsed/{{ session.demoname }}/voice/{{ guid }}.demopus", name: "{{ guid }}", info: "{{ psess.player.name }}"},
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
]).then(function() {
|
]).then(function() {
|
||||||
|
@ -40,3 +40,16 @@ def session(session_id):
|
|||||||
chats=chats,
|
chats=chats,
|
||||||
events=events
|
events=events
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@bp.route('/player/<guid>')
|
||||||
|
def player(guid):
|
||||||
|
player = Player.query.filter_by(guid=guid).one()
|
||||||
|
if not player:
|
||||||
|
return '404 Not Found', 404
|
||||||
|
|
||||||
|
player_sessions = PlayerSession.query.filter_by(player_guid=guid).all()
|
||||||
|
|
||||||
|
return render_template('player.html',
|
||||||
|
player=player,
|
||||||
|
player_sessions=player_sessions
|
||||||
|
)
|
||||||
|
@ -125,7 +125,7 @@ def parse_demo(path):
|
|||||||
kills = models.Player.kills + obj['kills'],
|
kills = models.Player.kills + obj['kills'],
|
||||||
voicetime = models.Player.voicetime + obj['voicetime'],
|
voicetime = models.Player.voicetime + obj['voicetime'],
|
||||||
first_seen = func.least(models.Player.first_seen, starttime),
|
first_seen = func.least(models.Player.first_seen, starttime),
|
||||||
last_seen = func.greatest(models.Player.first_seen, starttime),
|
last_seen = func.greatest(models.Player.last_seen, starttime),
|
||||||
))
|
))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
break
|
break
|
||||||
@ -225,9 +225,11 @@ def parse_demo(path):
|
|||||||
eventdata = obj.copy()
|
eventdata = obj.copy()
|
||||||
del eventdata['tick']
|
del eventdata['tick']
|
||||||
del eventdata['event']
|
del eventdata['event']
|
||||||
|
if 'steamid' in eventdata:
|
||||||
|
del eventdata['steamid']
|
||||||
|
|
||||||
event = models.Event(
|
event = models.Event(
|
||||||
player_guid=guid,
|
player_guid=obj['steamid'] if 'steamid' in obj else None,
|
||||||
session_id=session.id,
|
session_id=session.id,
|
||||||
tick=obj['tick'],
|
tick=obj['tick'],
|
||||||
time=starttime + timedelta(seconds=obj['tick'] * tickinterval),
|
time=starttime + timedelta(seconds=obj['tick'] * tickinterval),
|
||||||
|
@ -12,6 +12,7 @@ orjson==3.5.2
|
|||||||
python-dotenv==0.17.1
|
python-dotenv==0.17.1
|
||||||
SQLAlchemy==1.4.15
|
SQLAlchemy==1.4.15
|
||||||
tqdm==4.60.0
|
tqdm==4.60.0
|
||||||
|
uWSGI==2.0.19.1
|
||||||
Werkzeug==2.0.0
|
Werkzeug==2.0.0
|
||||||
zope.event==4.5.0
|
zope.event==4.5.0
|
||||||
zope.interface==5.4.0
|
zope.interface==5.4.0
|
||||||
|
Loading…
Reference in New Issue
Block a user