Skip to content

Commit d600608

Browse files
committed
Add demo web application
1 parent d7347c7 commit d600608

6 files changed

Lines changed: 363 additions & 1 deletion

File tree

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/examples/

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,36 @@ To build updated JavaScript files for browser use in `dist/`, run:
7373
yarn build
7474
```
7575

76+
## Demo application
77+
78+
![Web demo](examples/web.png)
79+
80+
In the `examples/` directory of the Git repo, you can find a small demo web
81+
application using Mopidy.js. The left half of the screen shows what's
82+
currently playing and provides some basic playback controls. The right half of
83+
the screen shows the JSON-RPC messages and events that are sent back and forth
84+
to the server, hopefully giving some insight into what is available to
85+
Mopidy.js developers.
86+
87+
To run the demo application yourself:
88+
89+
1. Make sure the `http/allowed_origins` config value in your `mopidy.conf`
90+
includes `localhost:1234`.
91+
92+
2. Run Mopidy on your local machine, so that Mopidy's web interface becomes
93+
available at http://localhost:6680/.
94+
95+
3. Clone Mopidy.js from GitHub.
96+
97+
4. Run `yarn` to install dependencies.
98+
99+
5. Run `yarn start` to run the demo application at http://localhost:1234/.
100+
101+
This setup uses hot module reloading, so any changes you do to the demo
102+
application files, `examples/web.{html,js}`, will instantly be visible in
103+
your browser. Thus, this can serve as a nice playing ground to get to know
104+
the capabilities of Mopidy and Mopidy.js.
105+
76106
## Changelog
77107

78108
See [CHANGELOG.md](CHANGELOG.md).

examples/web.html

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>Mopidy.js demo</title>
6+
<style>
7+
html,
8+
body {
9+
box-sizing: border-box;
10+
margin: 0;
11+
padding: 0;
12+
height: 100%;
13+
}
14+
15+
body {
16+
background: #333;
17+
color: #ccc;
18+
font-family: 'Roboto', 'Open Sans', sans-serif;
19+
font-size: 1.2rem;
20+
line-height: 1.4;
21+
}
22+
23+
.container {
24+
display: grid;
25+
grid-template-columns: 1fr 1fr;
26+
align-content: stretch;
27+
height: 100%;
28+
}
29+
30+
.player {
31+
grid-column: 1;
32+
padding: 2rem;
33+
34+
font-weight: 300;
35+
text-align: center;
36+
}
37+
38+
.event-log {
39+
grid-column: 2;
40+
margin: 0;
41+
padding: 1rem;
42+
max-height: 100vh;
43+
overflow: auto;
44+
45+
background: #444;
46+
font-family: 'Ubuntu Sans Mono', 'Open Sans Mono', monospace;
47+
font-size: 0.7rem;
48+
line-height: 1.2;
49+
}
50+
51+
h1 {
52+
font-size: 1.4rem;
53+
font-weight: 300;
54+
}
55+
56+
button {
57+
background: #444;
58+
color: #ccc;
59+
border: none;
60+
padding: 0.4rem 0.8rem;
61+
}
62+
63+
button:hover {
64+
background: #555;
65+
}
66+
67+
button:active,
68+
button.active {
69+
color: #fff;
70+
}
71+
72+
code {
73+
padding: 0.1rem;
74+
}
75+
76+
.help {
77+
margin-top: 2rem;
78+
}
79+
80+
.cover {
81+
margin: auto;
82+
max-width: 80%;
83+
}
84+
.cover img {
85+
height: auto;
86+
max-width: 100%;
87+
}
88+
89+
.controls p {
90+
display: inline-block;
91+
margin: 0.5rem;
92+
text-align: center;
93+
}
94+
95+
.state {
96+
font-size: 0.8rem;
97+
}
98+
</style>
99+
<script src="web.js"></script>
100+
<script defer src="https://use.fontawesome.com/releases/v5.4.1/js/all.js" integrity="sha384-L469/ELG4Bg9sDQbl0hvjMq8pOcqFgkSpwhwnslzvVVGpDjYJ6wJJyYjvG3u8XW7"
101+
crossorigin="anonymous"></script>
102+
</head>
103+
104+
<body>
105+
106+
<div class="container">
107+
<div class="player">
108+
109+
<div class="help offline-only">
110+
<h1>Server is offline</h1>
111+
112+
<p>
113+
Make sure the Mopidy config <code>http/allowed_origins</code> includes
114+
<code id="host">Unknown</code>.
115+
</p>
116+
117+
<p>
118+
Then start Mopidy so that the web server is available at
119+
http://localhost:6680/.
120+
</p>
121+
</div>
122+
123+
<div class="cover online-only">
124+
<img id="cover">
125+
</div>
126+
127+
<p class="current online-only">
128+
<span id="current-track">Unknown</span><br>
129+
<span id="current-artist">Unknown</span> -
130+
<span id="current-album">Unknown</span><br>
131+
</p>
132+
133+
<div class="controls online-only">
134+
<p id="playback-controls">
135+
<button id="previous" title="Previous"><i class="fas fa-step-backward"></i></button>
136+
<button id="play" title="Play"><i class="fas fa-play"></i></button>
137+
<button id="pause" title="Pause"><i class="fas fa-pause"></i></button>
138+
<button id="next" title="Next"><i class="fas fa-step-forward"></i></button>
139+
</p>
140+
<p id="tracklist-mode">
141+
<button id="repeat" title="Repeat"><i class="fas fa-redo"></i></button>
142+
<button id="random" title="Random"><i class="fas fa-random"></i></button>
143+
<button id="single" title="Single"><i class="fas fa-bullseye"></i></button>
144+
<button id="consume" title="Consume"><i class="fas fa-cookie-bite"></i></button>
145+
</p>
146+
</div>
147+
148+
<p class="state online-only">
149+
<span id="playback-state"></span><br>
150+
<code id="current-uri"></code>
151+
</p>
152+
153+
<div class="help online-only">
154+
<p>
155+
Open the developer console and use the <code>mopidy</code>
156+
variable to explore the API.
157+
</p>
158+
</div>
159+
160+
</div>
161+
162+
<pre class="event-log" id="event-log"></pre>
163+
</div>
164+
</div>
165+
166+
</body>
167+
168+
</html>

examples/web.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/* global window */
2+
/* eslint no-console:off, camelcase:off */
3+
4+
import Mopidy from "../mopidy";
5+
6+
const mopidy = new Mopidy({
7+
webSocketUrl: "ws://localhost:6680/mopidy/ws",
8+
});
9+
10+
// Make instance available through developer console
11+
window.mopidy = mopidy;
12+
13+
// Utilities
14+
15+
function el(id) {
16+
return document.getElementById(id);
17+
}
18+
19+
function hide(selector) {
20+
document.querySelectorAll(selector).forEach(e => {
21+
e.hidden = true;
22+
});
23+
}
24+
25+
function show(selector) {
26+
document.querySelectorAll(selector).forEach(e => {
27+
e.hidden = false;
28+
});
29+
}
30+
31+
// Event log
32+
33+
function appendToEventLog(type, data) {
34+
const log = el("event-log");
35+
log.insertAdjacentHTML(
36+
"beforeend",
37+
`<strong>${new Date().toISOString()} ${type}</strong><br>`
38+
);
39+
if (data) {
40+
log.insertAdjacentHTML("beforeend", JSON.stringify(data, null, 2));
41+
log.insertAdjacentHTML("beforeend", "<br>");
42+
}
43+
log.scrollTop = log.scrollHeight;
44+
}
45+
mopidy.on("state", appendToEventLog);
46+
mopidy.on("event", appendToEventLog);
47+
mopidy.on("websocket:incomingMessage", msg =>
48+
appendToEventLog("websocket:incomingMessage", JSON.parse(msg.data))
49+
);
50+
mopidy.on("websocket:outgoingMessage", data =>
51+
appendToEventLog("websocket:outgoingMessage", data)
52+
);
53+
54+
// Player
55+
56+
function updatePlaybackState(state, time_position) {
57+
if (time_position) {
58+
el("playback-state").innerText = `${state} at ${time_position / 1000}s`;
59+
} else {
60+
el("playback-state").innerText = state;
61+
}
62+
63+
switch (state) {
64+
case "playing":
65+
el("play").hidden = true;
66+
el("pause").hidden = false;
67+
break;
68+
case "paused":
69+
case "stopped":
70+
el("play").hidden = false;
71+
el("pause").hidden = true;
72+
break;
73+
default:
74+
}
75+
}
76+
77+
function updateCover(trackUri, images) {
78+
const [image] = images[trackUri];
79+
el("cover").setAttribute("src", image.uri);
80+
el("cover").setAttribute("height", image.height);
81+
el("cover").setAttribute("width", image.width);
82+
}
83+
84+
function updateCurrentTrack(track) {
85+
const artists = track.artists.map(a => a.name).join(", ");
86+
let albumName = track.album.name;
87+
if (track.album.date) {
88+
albumName = `${albumName} (${track.album.date})`;
89+
}
90+
91+
el("current-artist").innerText = artists;
92+
el("current-album").innerText = albumName;
93+
el("current-track").innerText = track.name;
94+
el("current-uri").innerText = track.uri;
95+
96+
mopidy.library
97+
.getImages([[track.uri]])
98+
.then(result => updateCover(track.uri, result));
99+
}
100+
101+
// Event handlers
102+
103+
mopidy.on("state:online", () => {
104+
hide(".offline-only");
105+
show(".online-only");
106+
107+
mopidy.playback.getState().then(updatePlaybackState);
108+
mopidy.playback.getCurrentTrack().then(updateCurrentTrack);
109+
110+
el("play").onclick = () => mopidy.playback.play();
111+
el("pause").onclick = () => mopidy.playback.pause();
112+
el("previous").onclick = () => mopidy.playback.previous();
113+
el("next").onclick = () => mopidy.playback.next();
114+
115+
el("repeat").onclick = e =>
116+
mopidy.tracklist.getRepeat().then(state =>
117+
mopidy.tracklist.setRepeat([!state]).then(() => {
118+
e.className = "active";
119+
})
120+
);
121+
el("random").onclick = () =>
122+
mopidy.tracklist
123+
.getRandom()
124+
.then(state => mopidy.tracklist.setRandom([!state]));
125+
el("single").onclick = () =>
126+
mopidy.tracklist
127+
.getSingle()
128+
.then(state => mopidy.tracklist.setSingle([!state]))
129+
.catch(console.error)
130+
.done();
131+
el("consume").onclick = () =>
132+
mopidy.tracklist
133+
.getConsume()
134+
.then(state => mopidy.tracklist.setConsume([!state]));
135+
});
136+
137+
mopidy.on("state:offline", () => {
138+
hide(".online-only");
139+
show(".offline-only");
140+
});
141+
142+
mopidy.on("event:playbackStateChanged", ({ new_state }) => {
143+
updatePlaybackState(new_state);
144+
});
145+
146+
mopidy.on("event:trackPlaybackStarted", ({ tl_track }) => {
147+
updateCurrentTrack(tl_track.track);
148+
});
149+
150+
mopidy.on("event:trackPlaybackStopped", () => {
151+
updatePlaybackState("stopped");
152+
});
153+
154+
mopidy.on("event:trackPlaybackPaused", ({ time_position }) => {
155+
updatePlaybackState("paused", time_position);
156+
});
157+
158+
mopidy.on("event:trackPlaybackResumed", () => {});
159+
160+
window.onload = () => {
161+
el("host").innerText = document.location.host;
162+
};

examples/web.png

239 KB
Loading

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
},
3636
"scripts": {
3737
"test": "jest",
38-
"build": "parcel build mopidy.js"
38+
"build": "parcel build mopidy.js",
39+
"start": "parcel examples/web.html"
3940
},
4041
"dependencies": {
4142
"isomorphic-ws": "^4.0.1",

0 commit comments

Comments
 (0)