From 1b551a5b8f06d8c7ecb85401878360b8c9544aaf Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 15 Jan 2021 10:56:32 -0600 Subject: [PATCH 1/7] Increment and reset viewer count when appropriate --- src/cleanup.ts | 3 ++- src/server.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cleanup.ts b/src/cleanup.ts index 17e134e..6ae774f 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -27,7 +27,8 @@ async function init() { //If satyr is restarted in the middle of a stream //it causes problems //Live flags in the database stay live - await db.query('update user_meta set live=false'); + await db.query('update user_meta set live=false'); + await db.query('update user_meta set viewers=0'); } async function bringUpToDate(): Promise{ diff --git a/src/server.ts b/src/server.ts index 1546a86..c86c097 100644 --- a/src/server.ts +++ b/src/server.ts @@ -93,6 +93,8 @@ function init () { db.query('update user_meta,users set user_meta.live=false where users.stream_key='+db.raw.escape(key)); db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then(async (results) => { if(results[0]) keystore.rm(results[0].username); + // reset viewer count when finished with stream + db.query('update user_meta set viewers = 0 where username='+db.raw.escape(results[0].username)); }); } @@ -119,6 +121,8 @@ function init () { if(app === config['media']['publicEndpoint']) { if(keystore[key]){ session.playStreamPath = '/'+config['media']['privateEndpoint']+'/'+keystore[key]; + // increment viewer count + db.query('update user_meta set viewers = viewers + 1 where username='+db.raw.escape(key)); return true; } } -- 2.38.3 From 3d131980ae4f7606c533de0debece895ffeb6431 Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 15 Jan 2021 11:05:01 -0600 Subject: [PATCH 2/7] Add database migrate for viewer tracking --- src/db/4.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/db/4.ts diff --git a/src/db/4.ts b/src/db/4.ts new file mode 100644 index 0000000..caa65d5 --- /dev/null +++ b/src/db/4.ts @@ -0,0 +1,8 @@ +import * as db from "../database"; + +async function run () { + await db.query('ALTER TABLE user_meta ADD viewers int UNSIGNED DEFAULT 0 AFTER live'); + await db.query('INSERT INTO db_meta (version) VALUES (4)'); +} + +export { run } \ No newline at end of file -- 2.38.3 From f966bda4dd1c4040af4682a3643b782bb9d73817 Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 15 Jan 2021 11:07:45 -0600 Subject: [PATCH 3/7] Return viewer count at a couple places in the API --- src/api.ts | 4 ++-- src/http.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api.ts b/src/api.ts index f1e40a2..d064e61 100644 --- a/src/api.ts +++ b/src/api.ts @@ -86,7 +86,7 @@ async function getConfig(username: string, all?: boolean): Promise{ if(all) { let users = await db.query('SELECT stream_key,record_flag FROM users WHERE username='+db.raw.escape(username)); if(users[0]) Object.assign(t, users[0]); - let usermeta = await db.query('SELECT title,about,live FROM user_meta WHERE username='+db.raw.escape(username)); + let usermeta = await db.query('SELECT title,about,live,viewers FROM user_meta WHERE username='+db.raw.escape(username)); if(usermeta[0]) Object.assign(t, usermeta[0]); let ci = await db.query('SELECT irc,xmpp,twitch,discord FROM chat_integration WHERE username='+db.raw.escape(username)); if(ci[0]) Object.assign(t, ci[0]); @@ -94,7 +94,7 @@ async function getConfig(username: string, all?: boolean): Promise{ if(tw[0]) t['twitch_mirror'] = Object.assign({}, tw[0]); } else { - let um = await db.query('SELECT title,about,live FROM user_meta WHERE username='+db.raw.escape(username)); + let um = await db.query('SELECT title,about,live,viewers FROM user_meta WHERE username='+db.raw.escape(username)); if(um[0]) Object.assign(t, um[0]); } return t; diff --git a/src/http.ts b/src/http.ts index 3bc1753..206ffa3 100644 --- a/src/http.ts +++ b/src/http.ts @@ -187,7 +187,7 @@ async function initAPI() { }); }); app.post('/api/users/live', (req, res) => { - let qs = 'SELECT username,title FROM user_meta WHERE live=1'; + let qs = 'SELECT username,title,viewers FROM user_meta WHERE live=1'; if(req.body.sort) { switch (req.body.sort) { @@ -217,7 +217,7 @@ async function initAPI() { }); }); app.post('/api/users/all', (req, res) => { - let qs = 'SELECT username,title,live FROM user_meta'; + let qs = 'SELECT username,title,live,viewers FROM user_meta'; if(req.body.sort) { switch (req.body.sort) { -- 2.38.3 From 21a85fa26c75867d8d152a8b47a2e8957bf3d711 Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 15 Jan 2021 20:27:02 -0600 Subject: [PATCH 4/7] Decrement viewer count when appropriate --- src/http.ts | 2 ++ src/server.ts | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/http.ts b/src/http.ts index 206ffa3..e154fc8 100644 --- a/src/http.ts +++ b/src/http.ts @@ -602,6 +602,7 @@ async function initChat() { } socket.join(data); io.to(data).emit('JOINED', {nick: socket.nick, room: data}); + db.query('update user_meta set viewers = viewers + 1 where username='+db.raw.escape(data)); } else socket.emit('ALERT', 'Room does not exist'); }); @@ -622,6 +623,7 @@ async function initChat() { }); socket.on('LEAVEROOM', (data) => { io.to(data).emit('LEFT', {nick: socket.nick, room: data}); + db.query('update user_meta set viewers = viewers - 1 where username='+db.raw.escape(data)); socket.leave(data); }); socket.on('disconnecting', (reason) => { diff --git a/src/server.ts b/src/server.ts index c86c097..8f05596 100644 --- a/src/server.ts +++ b/src/server.ts @@ -93,8 +93,6 @@ function init () { db.query('update user_meta,users set user_meta.live=false where users.stream_key='+db.raw.escape(key)); db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then(async (results) => { if(results[0]) keystore.rm(results[0].username); - // reset viewer count when finished with stream - db.query('update user_meta set viewers = 0 where username='+db.raw.escape(results[0].username)); }); } @@ -127,6 +125,18 @@ function init () { } } }); + nms.on('donePlay', (id, StreamPath, args) => { + let session = nms.getSession(id); + let app: string = StreamPath.split("/")[1]; + let key: string = StreamPath.split("/")[2]; + if(!session.isLocal && app === config['media']['publicEndpoint']) { + if(keystore[key]){ + // increment viewer count + db.query('update user_meta set viewers = viewers - 1 where username='+db.raw.escape(key)); + return true; + } + } + }); } async function transCommand(user: string, key: string): Promise{ -- 2.38.3 From bcba16014624b5b468636b9ceef50e13dd8f208d Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 15 Jan 2021 21:28:18 -0600 Subject: [PATCH 5/7] Update API documentation --- docs/REST.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/REST.md b/docs/REST.md index 92dbcce..53b5db8 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -55,7 +55,7 @@ play from: rtmp://example.com/live/username or https://example.com/live/username ## /api/users/live/ -Returns the usernames and stream titles of 10 users who are currently streaming +Returns the usernames and stream titles of 10 users who are currently streaming, and how many viewers they have. **Method**: POST @@ -80,7 +80,7 @@ The array will be wrapped in a JSON object under the key 'users'. Same as above, but returns all users regardless of whether they are streaming and if they're streaming or not. Also returns a value 'live' indicating whether a user is currently streaming. -**Example**: `{users: [{username:"foo", title:"bar", live:1}] }` +**Example**: `{users: [{username:"foo", title:"bar", live:1, viewers:10}] }` @@ -217,9 +217,9 @@ Get information about the specified user. **Parameters**: user -**Response**: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title, bio, and whether the stream is live. In the case of searching for a user that does not exist, the returned object will contain only the username searched for. +**Response**: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title, bio, viewers and whether the stream is live. In the case of searching for a user that does not exist, the returned object will contain only the username searched for. -**Example**: `{username: "foo", title: "bar", about: "This is an example bio", live: 0}` +**Example**: `{username: "foo", title: "bar", about: "This is an example bio", live: 0, viewers: 10}` -- 2.38.3 From dd940ff46f7d4a0628b13a714ebfa416bb8a2d9d Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 15 Jan 2021 21:28:38 -0600 Subject: [PATCH 6/7] Fix bugs with updating viewer count --- src/http.ts | 1 + src/server.ts | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/http.ts b/src/http.ts index e154fc8..697e378 100644 --- a/src/http.ts +++ b/src/http.ts @@ -630,6 +630,7 @@ async function initChat() { let rooms = Object.keys(socket.rooms); for(let i=1;i { - db.query('select username,title,about from user_meta where username='+db.raw.escape(req.params.user)).then((result) => { + db.query('select username,title,about,viewers from user_meta where username='+db.raw.escape(req.params.user)).then((result) => { if(result[0]){ if(tryDecode(req.cookies.Authorization)) { res.render('user.njk', Object.assign(result[0], {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf)); diff --git a/templates/user.njk b/templates/user.njk index f6fc56c..29e048e 100644 --- a/templates/user.njk +++ b/templates/user.njk @@ -5,7 +5,6 @@ shakaPolyFilled = false; var manifestUri = document.location.protocol+'//'+document.location.host+'/live/{{ username }}/index.mpd'; async function initPlayer() { - console.log('Trying to initialize player.'); if(!shakaPolyFilled){ shaka.polyfill.installAll(); shakaPolyFilled = true; @@ -58,9 +57,22 @@ function newPopup(url) { popupWindow = window.open( url,'popUpWindow','height=700,width=450,scrollbars=yes,toolbar=no,menubar=no,location=no,directories=no,status=yes') } +async function updateViewers(){ + let vc = document.getElementById('viewercount'); + if(!vc) return false; + let path = window.location.pathname; + if(path.substring(path.length - 1).indexOf('/') !== -1) + var usr = path.substring(7, path.length - 1); + else var usr = path.substring(7); + let viewers = JSON.parse(await makeRequest("GET", "/api/"+usr+"/config")).viewers; + vc.innerHTML = "[Viewers: "+viewers+"]"; + setTimeout(updateViewers, 2000); +}
- {{ username }} | {{ title | escape }} Links | Watch Chat VODs + {{ username }} | {{ title | escape }}[Viewers: {{viewers}}] + + Links | Watch Chat VODs
Play Stream -- 2.38.3