wplayer

Check-in [31f5dff385]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Save scroll position so you continue from wherever you left off at a given folder level. Add keyboard shortcuts (need fixing, sees search input). Add "love" with logging to var/
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256:31f5dff3856da44d8017c865339075aa7184f4e56a34ef85a0526e286327cab3
User & Date: ajv-899-334-8894@vsta.org 2018-03-13 01:44:42
Context
2018-03-13
01:50
Don't use global keystroke shortcuts when it's input. check-in: b93e987eac user: ajv-899-334-8894@vsta.org tags: master, trunk
01:44
Save scroll position so you continue from wherever you left off at a given folder level. Add keyboard shortcuts (need fixing, sees search input). Add "love" with logging to var/ check-in: 31f5dff385 user: ajv-899-334-8894@vsta.org tags: master, trunk
2017-11-22
04:21
Embrace ES6 now that we can play flac on FF using transcoding. Shorten playlist entries when they have a common path. check-in: bc0d6d6ec2 user: ajv-899-334-8894@vsta.org tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to html/top.html.

21
22
23
24
25
26
27


28
29
30
31
32
33
34
    <button onclick="return seek(-600);">-10m</button>
    <button onclick="return seek(-60);">-1m</button>
    <button onclick="return seek(-10);">-10s</button>
    <button onclick="return seek(10);">+10s</button>
    <button onclick="return seek(60);">+1m</button>
    <button onclick="return seek(600);">+10m</button>
    <button onclick="return playNext();">>next</button>


   </td>
  </tr>
 </table>
 <hr>
 <button onclick="return go_up();">
  <img src="/imgs/player_eject.png" alt="Go Up" />
 </button>







>
>







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    <button onclick="return seek(-600);">-10m</button>
    <button onclick="return seek(-60);">-1m</button>
    <button onclick="return seek(-10);">-10s</button>
    <button onclick="return seek(10);">+10s</button>
    <button onclick="return seek(60);">+1m</button>
    <button onclick="return seek(600);">+10m</button>
    <button onclick="return playNext();">>next</button>
    <button onclick="return shuffle();">shufl</button>
    <button onclick="return loveit();" id="lovebtn">love</button>
   </td>
  </tr>
 </table>
 <hr>
 <button onclick="return go_up();">
  <img src="/imgs/player_eject.png" alt="Go Up" />
 </button>

Changes to js/player.js.

31
32
33
34
35
36
37


38
39
40
41
42
43
44
..
52
53
54
55
56
57
58



















59
60
61
62
63
64
65
...
107
108
109
110
111
112
113



114
115
116
117
118
119
120
...
184
185
186
187
188
189
190

191
192
193
194
195
196
197
...
239
240
241
242
243
244
245




246
247
248
249
250
251
252
...
296
297
298
299
300
301
302

303
304
305
306
307
308
309
310
311

312
313
314
315
316
317
318
...
331
332
333
334
335
336
337
338



























339
340



















341
342
343
var path = "";
var pdepth = 0;
// Are we within a tree with a file index?
var indexed = false;
// This will be path's contents; it goes to null while
//  it's getting new content for the current path
var pcontents = null;



// Do we have something on the turntable right now?
var playing = false;

// One-time parse of base URL to find any player options
// Note we don't do any defense here, because the URL is under the
//  remote user's control, but it's only their own browser's JS which
................................................................................
        opts[item[0]] = true;
    } else {
        opts[item[0]] = item[1];
    }
  });
};
urlOpts();




















// Answer back on dir contents
function gotDir(req) {
    var d, f, res;

    // Connection progress
    if ((req.readyState != 4) || (req.status != 200)) {
................................................................................
	    res += ' <img src="/imgs/player_play.png" alt="play" />\n'
	    res += '</button>' + unescape(f) + '<br>\n'
	}
    }

    // Replace the HTML
    browser.innerHTML = res;



}

// Invert a path and make it friendlier to small screens
function do_trim(s) {
    var idx, elem, res = '';

    while (s.length > 0) {
................................................................................
    if (newTime >= 0) {
	player.currentTime = newTime;
    }
}

// End of current track, try to advance
function playNext() {

    if (playlist.length > 0) {
	var s = playlist.shift();
	player.src = s;

	// For good measure
	player.play();

................................................................................
    // If looking at search filter, "up" means back out
    //  of this filtered view
    if (searchval.value != "") {
        searchval.value = "";
        curdir();
        return false;
    }





    // Otherwise pop up a directory level
    var idx = path.lastIndexOf("/");
    path = path.slice(0, idx);
    pdepth -= 1;
    curdir();

................................................................................

// Push to dir
function go_dir(didx) {
    // Working on a previous click
    if (pcontents == null) {
	return false;
    }

    path = path + "/" + pcontents.dirs[didx];
    pdepth += 1;
    searchval.value = "";
    curdir();
    return false;
}

// Back to top of tree
function go_top() {

    path = "";
    pdepth = 0;
    curdir();
    return false;
}

// Incremental search
................................................................................

    // Get a set of options based on the current path
    //  and search value
    var url = "/search" + path + "?q=" + escape(s);
    pdepth += 1;
    getDir(url);
}




























// Set up the player
player.addEventListener("ended", playNext);




















// Initial browser window update
curdir();







>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>







 







>







 







>
>
>
>







 







>









>







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
..
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
...
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
...
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
...
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
...
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
var path = "";
var pdepth = 0;
// Are we within a tree with a file index?
var indexed = false;
// This will be path's contents; it goes to null while
//  it's getting new content for the current path
var pcontents = null;
// This maps a given path to our last scroll position within it
var pscroll = {};

// Do we have something on the turntable right now?
var playing = false;

// One-time parse of base URL to find any player options
// Note we don't do any defense here, because the URL is under the
//  remote user's control, but it's only their own browser's JS which
................................................................................
        opts[item[0]] = true;
    } else {
        opts[item[0]] = item[1];
    }
  });
};
urlOpts();

// Note current scroll position WRT current path
function markpos() {
    if (searchval.value == '') {
	var scroller = document.scrollingElement;
	pscroll[path] = scroller.scrollTop;
    }
}
// Restore to scroll position
function gopos() {
    if (searchval.value != '') {
	return;
    }
    if (path in pscroll) {
	var d = document.scrollingElement;
	d.scrollTop = pscroll[path];
    }
}


// Answer back on dir contents
function gotDir(req) {
    var d, f, res;

    // Connection progress
    if ((req.readyState != 4) || (req.status != 200)) {
................................................................................
	    res += ' <img src="/imgs/player_play.png" alt="play" />\n'
	    res += '</button>' + unescape(f) + '<br>\n'
	}
    }

    // Replace the HTML
    browser.innerHTML = res;

    // Auto-scroll, but leave search alone
    gopos();
}

// Invert a path and make it friendlier to small screens
function do_trim(s) {
    var idx, elem, res = '';

    while (s.length > 0) {
................................................................................
    if (newTime >= 0) {
	player.currentTime = newTime;
    }
}

// End of current track, try to advance
function playNext() {
    lovebtn.style.background = "";
    if (playlist.length > 0) {
	var s = playlist.shift();
	player.src = s;

	// For good measure
	player.play();

................................................................................
    // If looking at search filter, "up" means back out
    //  of this filtered view
    if (searchval.value != "") {
        searchval.value = "";
        curdir();
        return false;
    }

    // It's a "real" up, note position so if we come back down,
    //  we'll still be at this position.
    markpos();

    // Otherwise pop up a directory level
    var idx = path.lastIndexOf("/");
    path = path.slice(0, idx);
    pdepth -= 1;
    curdir();

................................................................................

// Push to dir
function go_dir(didx) {
    // Working on a previous click
    if (pcontents == null) {
	return false;
    }
    markpos();
    path = path + "/" + pcontents.dirs[didx];
    pdepth += 1;
    searchval.value = "";
    curdir();
    return false;
}

// Back to top of tree
function go_top() {
    markpos();
    path = "";
    pdepth = 0;
    curdir();
    return false;
}

// Incremental search
................................................................................

    // Get a set of options based on the current path
    //  and search value
    var url = "/search" + path + "?q=" + escape(s);
    pdepth += 1;
    getDir(url);
}

// Shuffle current playlist
function shuffle() {
    var idx, other, idx, hold;
    var l = playlist.length;

    for (idx = 0; idx < l; ++idx) {
	other = Math.floor(Math.random() * l);
	hold = playlist[idx];
	playlist[idx] = playlist[other];
	playlist[other] = hold;
    }
    paintQueue();
    return false;
}

// Note a track we like
function loveit() {
    if (!playing) {
	return;
    }
    lovebtn.style.background = "pink";
    var ob = {"url": player.src};
    var req = new XMLHttpRequest();
    req.open("PUT", "/love.json");
    req.send(JSON.stringify(ob));
}

// Set up the player
player.addEventListener("ended", playNext);

// A few keyboard shortcuts
document.onkeypress = function(e) {
    e = e || window.event;
    var kc = e.keyCode;

    // 'b', go back
    if (kc == 98) {
	go_up();

    // 't', go to top
    } else if (kc == 116) {
	var d = document.scrollingElement;
	d.scrollTop = 0;

    } else {
	console.log("Unknown keystroke: " + kc.toString());
    }
}

// Initial browser window update
curdir();

Changes to main.py.

2
3
4
5
6
7
8

9
10
11
12
13
14
15
16

17
18
19
20
21
22
23
# main.py
#	Main driver for WWW-Usenet interface
#
import sys, time, os
import chore
import utils
from get import GET_mixin

from chore.authen import Authen_mixin, Authen_Server_mixin

# Tie our various handlers together
class App_Handler(chore.handlers.Chore_Handler, GET_mixin,
        Authen_mixin):
    def __init__(self, conn, tup, approot):
	chore.handlers.Chore_Handler.__init__(self, conn, tup, approot,
	    (GET_mixin.__init__,

             Authen_mixin.__init__))

# Load our configuration file
#
# This includes configuring our config file elements,
#  then processing the supplied file.
def load_cfg(fn):







>




|



>







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# main.py
#	Main driver for WWW-Usenet interface
#
import sys, time, os
import chore
import utils
from get import GET_mixin
from put import PUT_mixin
from chore.authen import Authen_mixin, Authen_Server_mixin

# Tie our various handlers together
class App_Handler(chore.handlers.Chore_Handler, GET_mixin,
        PUT_mixin, Authen_mixin):
    def __init__(self, conn, tup, approot):
	chore.handlers.Chore_Handler.__init__(self, conn, tup, approot,
	    (GET_mixin.__init__,
	     PUT_mixin.__init__,
             Authen_mixin.__init__))

# Load our configuration file
#
# This includes configuring our config file elements,
#  then processing the supplied file.
def load_cfg(fn):

Deleted post.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#
# post.py
#	HTML POST/PUT handling
#
# POST's put state back to us--usually a text to send.
#
# / - An old-skool Form submit; non-JS browser
# /msg.json - JSON of a message to send
#
import json
from utils import toascii

class POST_mixin(object):

    # Write @buf's contents to the file named @fn
    def putfile(self, fn, buf):
	f = open(fn, "w")
	f.write(buf)
	f.close()

    # Send a message
    def post_msg(self, buf):
	# Burst from JSON
	msg = json.loads(buf)
	towhom = toascii(msg.get("recipient", ""))
	body = toascii(msg.get("body", ""))

	# Malformed?
	if (not towhom) or (not body):
	    self.send_error(404)
	    return None

	# Recipient known?
	user = self.user
	acct = user.roster.get(towhom)
	if acct is None:
	    self.send_error(404)
	    return None

	# Send message
	with user.exclusion:
	    print "Send msg", towhom, body
	    acct.send(towhom, body)

	# Log a message on our own display
	for tup in reversed(user.msgs):
	    if tup[1] != '>':
		break
	if towhom == tup[1]:
	    # "mFrom" is what gets displayed, so populate it with
	    #  either '>' (last sender) or actual name
	    user.add('>', towhom, body)
	else:
	    user.add('> ' + towhom, towhom, body)

	# Empty result body
	self.send_result("")
	return ""

    # Entry from an HTML POST, body already read
    #  and in @buf
    def handle_post(self, buf):
	p = self.path

	# Burst path
	pp = p.strip("/").split("/")

	# Operation on root; Form submit, so probably an old
	#  non-JS browser sending a message.
	if (len(pp) == 1) and (not pp[0]):
	    print "TBD: POST to root"
	    # return self.post_root(buf)
	    self.send_error(404)
	    return None

	# JS submission of text to post
	if (len(pp) == 1) and (pp[0] == "msg.json"):
	    return self.post_msg(buf)

	# Unknown submission type
	self.send_error(404)
	return None
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































Added put.py.















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#
# put.py
#	HTML PUT handling
#
# /love.json
#	Flag a track (well, URL) of something we like
#
import json

class PUT_mixin(object):

    # URL handlers
    def __init__(self):
	self.dispatchers.append( ("PUT", self.loveit) )

    # Accept a love'ed track and log to this user
    def loveit(self, buf):
	d = json.loads(buf)
	f = open("var/" + self.user + ".loves", "a")
	f.write(d["url"])
	f.write("\n")
	f.close()
	return True,self.send_result("", "text/html")