bumble 0.0.154__py3-none-any.whl → 0.0.156__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/a2dp.py +1 -0
- bumble/apps/pair.py +14 -14
- bumble/apps/scan.py +5 -6
- bumble/apps/speaker/__init__.py +0 -0
- bumble/apps/speaker/logo.svg +42 -0
- bumble/apps/speaker/speaker.css +76 -0
- bumble/apps/speaker/speaker.html +34 -0
- bumble/apps/speaker/speaker.js +315 -0
- bumble/apps/speaker/speaker.py +747 -0
- bumble/apps/unbond.py +40 -22
- bumble/avdtp.py +50 -31
- bumble/codecs.py +381 -0
- bumble/device.py +17 -4
- bumble/hci.py +13 -9
- bumble/hfp.py +14 -8
- bumble/host.py +7 -1
- bumble/keys.py +72 -46
- bumble/pandora/host.py +2 -1
- bumble/pandora/security.py +4 -7
- bumble/rfcomm.py +110 -67
- bumble/smp.py +1 -1
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/METADATA +5 -4
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/RECORD +28 -21
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/LICENSE +0 -0
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/WHEEL +0 -0
- {bumble-0.0.154.dist-info → bumble-0.0.156.dist-info}/top_level.txt +0 -0
bumble/_version.py
CHANGED
bumble/a2dp.py
CHANGED
bumble/apps/pair.py
CHANGED
|
@@ -207,7 +207,7 @@ def on_connection(connection, request):
|
|
|
207
207
|
|
|
208
208
|
# Listen for pairing events
|
|
209
209
|
connection.on('pairing_start', on_pairing_start)
|
|
210
|
-
connection.on('pairing', on_pairing)
|
|
210
|
+
connection.on('pairing', lambda keys: on_pairing(connection.peer_address, keys))
|
|
211
211
|
connection.on('pairing_failure', on_pairing_failure)
|
|
212
212
|
|
|
213
213
|
# Listen for encryption changes
|
|
@@ -242,9 +242,9 @@ def on_pairing_start():
|
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
# -----------------------------------------------------------------------------
|
|
245
|
-
def on_pairing(keys):
|
|
245
|
+
def on_pairing(address, keys):
|
|
246
246
|
print(color('***-----------------------------------', 'cyan'))
|
|
247
|
-
print(color('*** Paired!', 'cyan'))
|
|
247
|
+
print(color(f'*** Paired! (peer identity={address})', 'cyan'))
|
|
248
248
|
keys.print(prefix=color('*** ', 'cyan'))
|
|
249
249
|
print(color('***-----------------------------------', 'cyan'))
|
|
250
250
|
Waiter.instance.terminate()
|
|
@@ -283,17 +283,6 @@ async def pair(
|
|
|
283
283
|
# Create a device to manage the host
|
|
284
284
|
device = Device.from_config_file_with_hci(device_config, hci_source, hci_sink)
|
|
285
285
|
|
|
286
|
-
# Set a custom keystore if specified on the command line
|
|
287
|
-
if keystore_file:
|
|
288
|
-
device.keystore = JsonKeyStore(namespace=None, filename=keystore_file)
|
|
289
|
-
|
|
290
|
-
# Print the existing keys before pairing
|
|
291
|
-
if print_keys and device.keystore:
|
|
292
|
-
print(color('@@@-----------------------------------', 'blue'))
|
|
293
|
-
print(color('@@@ Pairing Keys:', 'blue'))
|
|
294
|
-
await device.keystore.print(prefix=color('@@@ ', 'blue'))
|
|
295
|
-
print(color('@@@-----------------------------------', 'blue'))
|
|
296
|
-
|
|
297
286
|
# Expose a GATT characteristic that can be used to trigger pairing by
|
|
298
287
|
# responding with an authentication error when read
|
|
299
288
|
if mode == 'le':
|
|
@@ -323,6 +312,17 @@ async def pair(
|
|
|
323
312
|
# Get things going
|
|
324
313
|
await device.power_on()
|
|
325
314
|
|
|
315
|
+
# Set a custom keystore if specified on the command line
|
|
316
|
+
if keystore_file:
|
|
317
|
+
device.keystore = JsonKeyStore.from_device(device, filename=keystore_file)
|
|
318
|
+
|
|
319
|
+
# Print the existing keys before pairing
|
|
320
|
+
if print_keys and device.keystore:
|
|
321
|
+
print(color('@@@-----------------------------------', 'blue'))
|
|
322
|
+
print(color('@@@ Pairing Keys:', 'blue'))
|
|
323
|
+
await device.keystore.print(prefix=color('@@@ ', 'blue'))
|
|
324
|
+
print(color('@@@-----------------------------------', 'blue'))
|
|
325
|
+
|
|
326
326
|
# Set up a pairing config factory
|
|
327
327
|
device.pairing_config_factory = lambda connection: PairingConfig(
|
|
328
328
|
sc, mitm, bond, Delegate(mode, connection, io, prompt)
|
bumble/apps/scan.py
CHANGED
|
@@ -133,15 +133,16 @@ async def scan(
|
|
|
133
133
|
'Bumble', 'F0:F1:F2:F3:F4:F5', hci_source, hci_sink
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
+
await device.power_on()
|
|
137
|
+
|
|
136
138
|
if keystore_file:
|
|
137
|
-
keystore = JsonKeyStore(
|
|
138
|
-
device.keystore = keystore
|
|
139
|
-
else:
|
|
140
|
-
resolver = None
|
|
139
|
+
device.keystore = JsonKeyStore.from_device(device, filename=keystore_file)
|
|
141
140
|
|
|
142
141
|
if device.keystore:
|
|
143
142
|
resolving_keys = await device.keystore.get_resolving_keys()
|
|
144
143
|
resolver = AddressResolver(resolving_keys)
|
|
144
|
+
else:
|
|
145
|
+
resolver = None
|
|
145
146
|
|
|
146
147
|
printer = AdvertisementPrinter(min_rssi, resolver)
|
|
147
148
|
if raw:
|
|
@@ -149,8 +150,6 @@ async def scan(
|
|
|
149
150
|
else:
|
|
150
151
|
device.on('advertisement', printer.on_advertisement)
|
|
151
152
|
|
|
152
|
-
await device.power_on()
|
|
153
|
-
|
|
154
153
|
if phy is None:
|
|
155
154
|
scanning_phys = [HCI_LE_1M_PHY, HCI_LE_CODED_PHY]
|
|
156
155
|
else:
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Vectornator for iOS (http://vectornator.io/) --><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
2
|
+
<svg height="100%" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" xmlns:vectornator="http://vectornator.io" version="1.1" viewBox="0 0 745 744.634">
|
|
3
|
+
<metadata>
|
|
4
|
+
<vectornator:setting key="DimensionsVisible" value="1"/>
|
|
5
|
+
<vectornator:setting key="PencilOnly" value="0"/>
|
|
6
|
+
<vectornator:setting key="SnapToPoints" value="0"/>
|
|
7
|
+
<vectornator:setting key="OutlineMode" value="0"/>
|
|
8
|
+
<vectornator:setting key="CMYKEnabledKey" value="0"/>
|
|
9
|
+
<vectornator:setting key="RulersVisible" value="1"/>
|
|
10
|
+
<vectornator:setting key="SnapToEdges" value="0"/>
|
|
11
|
+
<vectornator:setting key="GuidesVisible" value="1"/>
|
|
12
|
+
<vectornator:setting key="DisplayWhiteBackground" value="0"/>
|
|
13
|
+
<vectornator:setting key="doHistoryDisabled" value="0"/>
|
|
14
|
+
<vectornator:setting key="SnapToGuides" value="1"/>
|
|
15
|
+
<vectornator:setting key="TimeLapseWatermarkDisabled" value="0"/>
|
|
16
|
+
<vectornator:setting key="Units" value="Pixels"/>
|
|
17
|
+
<vectornator:setting key="DynamicGuides" value="0"/>
|
|
18
|
+
<vectornator:setting key="IsolateActiveLayer" value="0"/>
|
|
19
|
+
<vectornator:setting key="SnapToGrid" value="0"/>
|
|
20
|
+
</metadata>
|
|
21
|
+
<defs/>
|
|
22
|
+
<g id="Layer 1" vectornator:layerName="Layer 1">
|
|
23
|
+
<path stroke="#000000" stroke-width="18.6464" d="M368.753+729.441L58.8847+550.539L58.8848+192.734L368.753+13.8313L678.621+192.734L678.621+550.539L368.753+729.441Z" fill="#0082fc" stroke-linecap="butt" fill-opacity="0.307489" opacity="1" stroke-linejoin="round"/>
|
|
24
|
+
<g opacity="1">
|
|
25
|
+
<g opacity="1">
|
|
26
|
+
<path stroke="#000000" stroke-width="20" d="M292.873+289.256L442.872+289.256L442.872+539.254L292.873+539.254L292.873+289.256Z" fill="#fcd100" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
27
|
+
<path stroke="#000000" stroke-width="20" d="M292.873+289.256C292.873+247.835+326.452+214.257+367.873+214.257C409.294+214.257+442.872+247.835+442.872+289.256C442.872+330.677+409.294+364.256+367.873+364.256C326.452+364.256+292.873+330.677+292.873+289.256Z" fill="#fcd100" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
28
|
+
<path stroke="#000000" stroke-width="20" d="M292.873+539.254C292.873+497.833+326.452+464.255+367.873+464.255C409.294+464.255+442.872+497.833+442.872+539.254C442.872+580.675+409.294+614.254+367.873+614.254C326.452+614.254+292.873+580.675+292.873+539.254Z" fill="#fcd100" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
29
|
+
<path stroke="#0082fc" stroke-width="0.1" d="M302.873+289.073L432.872+289.073L432.872+539.072L302.873+539.072L302.873+289.073Z" fill="#fcd100" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
30
|
+
</g>
|
|
31
|
+
<path stroke="#000000" stroke-width="0.1" d="M103.161+309.167L226.956+443.903L366.671+309.604L103.161+309.167Z" fill="#0082fc" stroke-linecap="round" opacity="1" stroke-linejoin="round"/>
|
|
32
|
+
<path stroke="#000000" stroke-width="0.1" d="M383.411+307.076L508.887+440.112L650.5+307.507L383.411+307.076Z" fill="#0082fc" stroke-linecap="round" opacity="1" stroke-linejoin="round"/>
|
|
33
|
+
<path stroke="#000000" stroke-width="20" d="M522.045+154.808L229.559+448.882L83.8397+300.104L653.666+302.936L511.759+444.785L223.101+156.114" fill="none" stroke-linecap="round" opacity="1" stroke-linejoin="round"/>
|
|
34
|
+
<path stroke="#000000" stroke-width="61.8698" d="M295.857+418.738L438.9+418.738" fill="none" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
35
|
+
<path stroke="#000000" stroke-width="61.8698" d="M295.857+521.737L438.9+521.737" fill="none" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
36
|
+
<g opacity="1">
|
|
37
|
+
<path stroke="#0082fc" stroke-width="0.1" d="M367.769+667.024L367.821+616.383L403.677+616.336C383.137+626.447+368.263+638.69+367.769+667.024Z" fill="#000000" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
38
|
+
<path stroke="#0082fc" stroke-width="0.1" d="M367.836+667.024L367.784+616.383L331.928+616.336C352.468+626.447+367.341+638.69+367.836+667.024Z" fill="#000000" stroke-linecap="butt" opacity="1" stroke-linejoin="round"/>
|
|
39
|
+
</g>
|
|
40
|
+
</g>
|
|
41
|
+
</g>
|
|
42
|
+
</svg>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
body, h1, h2, h3, h4, h5, h6 {
|
|
2
|
+
font-family: sans-serif;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
#controlsDiv {
|
|
6
|
+
margin: 6px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#connectionText {
|
|
10
|
+
background-color: rgb(239, 89, 75);
|
|
11
|
+
border: none;
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
padding: 8px;
|
|
14
|
+
display: inline-block;
|
|
15
|
+
margin: 4px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#startButton {
|
|
19
|
+
padding: 4px;
|
|
20
|
+
margin: 6px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#fftCanvas {
|
|
24
|
+
border-radius: 16px;
|
|
25
|
+
margin: 6px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#bandwidthCanvas {
|
|
29
|
+
border: grey;
|
|
30
|
+
border-style: solid;
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
margin: 6px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#streamStateText {
|
|
36
|
+
background-color: rgb(93, 165, 93);
|
|
37
|
+
border: none;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
padding: 10px 20px;
|
|
40
|
+
display: inline-block;
|
|
41
|
+
margin: 6px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#connectionStateText {
|
|
45
|
+
background-color: rgb(112, 146, 206);
|
|
46
|
+
border: none;
|
|
47
|
+
border-radius: 8px;
|
|
48
|
+
padding: 10px 20px;
|
|
49
|
+
display: inline-block;
|
|
50
|
+
margin: 6px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#propertiesTable {
|
|
54
|
+
border: grey;
|
|
55
|
+
border-style: solid;
|
|
56
|
+
border-radius: 4px;
|
|
57
|
+
padding: 4px;
|
|
58
|
+
margin: 6px;
|
|
59
|
+
margin-left: 0px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
th, td {
|
|
63
|
+
padding-left: 6px;
|
|
64
|
+
padding-right: 6px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.properties td:nth-child(even) {
|
|
68
|
+
background-color: #D6EEEE;
|
|
69
|
+
font-family: monospace;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.properties td:nth-child(odd) {
|
|
73
|
+
font-weight: bold;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.properties tr td:nth-child(2) { width: 150px; }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Bumble Speaker</title>
|
|
5
|
+
<script type="text/javascript" src="speaker.js"></script>
|
|
6
|
+
<link rel="stylesheet" href="speaker.css">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1><img src="logo.svg" width=100 height=100 style="vertical-align:middle" alt=""/>Bumble Virtual Speaker</h1>
|
|
10
|
+
<div id="connectionText"></div>
|
|
11
|
+
<div id="speaker">
|
|
12
|
+
<table><tr>
|
|
13
|
+
<td>
|
|
14
|
+
<table id="propertiesTable" class="properties">
|
|
15
|
+
<tr><td>Codec</td><td><span id="codecText"></span></td></tr>
|
|
16
|
+
<tr><td>Packets</td><td><span id="packetsReceivedText"></span></td></tr>
|
|
17
|
+
<tr><td>Bytes</td><td><span id="bytesReceivedText"></span></td></tr>
|
|
18
|
+
</table>
|
|
19
|
+
</td>
|
|
20
|
+
<td>
|
|
21
|
+
<canvas id="bandwidthCanvas" width="500", height="100">Bandwidth Graph</canvas>
|
|
22
|
+
</td>
|
|
23
|
+
</tr></table>
|
|
24
|
+
<span id="streamStateText">IDLE</span>
|
|
25
|
+
<span id="connectionStateText">NOT CONNECTED</span>
|
|
26
|
+
<div id="controlsDiv">
|
|
27
|
+
<button id="audioOnButton">Audio On</button>
|
|
28
|
+
<span id="audioSupportMessageText"></span>
|
|
29
|
+
</div>
|
|
30
|
+
<canvas id="fftCanvas" width="1024", height="300">Audio Frequencies Animation</canvas>
|
|
31
|
+
<audio id="audio"></audio>
|
|
32
|
+
</div>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const channelUrl = ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/channel";
|
|
5
|
+
let channelSocket;
|
|
6
|
+
let connectionText;
|
|
7
|
+
let codecText;
|
|
8
|
+
let packetsReceivedText;
|
|
9
|
+
let bytesReceivedText;
|
|
10
|
+
let streamStateText;
|
|
11
|
+
let connectionStateText;
|
|
12
|
+
let controlsDiv;
|
|
13
|
+
let audioOnButton;
|
|
14
|
+
let mediaSource;
|
|
15
|
+
let sourceBuffer;
|
|
16
|
+
let audioElement;
|
|
17
|
+
let audioContext;
|
|
18
|
+
let audioAnalyzer;
|
|
19
|
+
let audioFrequencyBinCount;
|
|
20
|
+
let audioFrequencyData;
|
|
21
|
+
let packetsReceived = 0;
|
|
22
|
+
let bytesReceived = 0;
|
|
23
|
+
let audioState = "stopped";
|
|
24
|
+
let streamState = "IDLE";
|
|
25
|
+
let audioSupportMessageText;
|
|
26
|
+
let fftCanvas;
|
|
27
|
+
let fftCanvasContext;
|
|
28
|
+
let bandwidthCanvas;
|
|
29
|
+
let bandwidthCanvasContext;
|
|
30
|
+
let bandwidthBinCount;
|
|
31
|
+
let bandwidthBins = [];
|
|
32
|
+
|
|
33
|
+
const FFT_WIDTH = 800;
|
|
34
|
+
const FFT_HEIGHT = 256;
|
|
35
|
+
const BANDWIDTH_WIDTH = 500;
|
|
36
|
+
const BANDWIDTH_HEIGHT = 100;
|
|
37
|
+
|
|
38
|
+
function hexToBytes(hex) {
|
|
39
|
+
return Uint8Array.from(hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function init() {
|
|
43
|
+
initUI();
|
|
44
|
+
initMediaSource();
|
|
45
|
+
initAudioElement();
|
|
46
|
+
initAnalyzer();
|
|
47
|
+
|
|
48
|
+
connect();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function initUI() {
|
|
52
|
+
controlsDiv = document.getElementById("controlsDiv");
|
|
53
|
+
controlsDiv.style.visibility = "hidden";
|
|
54
|
+
connectionText = document.getElementById("connectionText");
|
|
55
|
+
audioOnButton = document.getElementById("audioOnButton");
|
|
56
|
+
codecText = document.getElementById("codecText");
|
|
57
|
+
packetsReceivedText = document.getElementById("packetsReceivedText");
|
|
58
|
+
bytesReceivedText = document.getElementById("bytesReceivedText");
|
|
59
|
+
streamStateText = document.getElementById("streamStateText");
|
|
60
|
+
connectionStateText = document.getElementById("connectionStateText");
|
|
61
|
+
audioSupportMessageText = document.getElementById("audioSupportMessageText");
|
|
62
|
+
|
|
63
|
+
audioOnButton.onclick = () => startAudio();
|
|
64
|
+
|
|
65
|
+
setConnectionText("");
|
|
66
|
+
|
|
67
|
+
requestAnimationFrame(onAnimationFrame);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function initMediaSource() {
|
|
71
|
+
mediaSource = new MediaSource();
|
|
72
|
+
mediaSource.onsourceopen = onMediaSourceOpen;
|
|
73
|
+
mediaSource.onsourceclose = onMediaSourceClose;
|
|
74
|
+
mediaSource.onsourceended = onMediaSourceEnd;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function initAudioElement() {
|
|
78
|
+
audioElement = document.getElementById("audio");
|
|
79
|
+
audioElement.src = URL.createObjectURL(mediaSource);
|
|
80
|
+
// audioElement.controls = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function initAnalyzer() {
|
|
84
|
+
fftCanvas = document.getElementById("fftCanvas");
|
|
85
|
+
fftCanvas.width = FFT_WIDTH
|
|
86
|
+
fftCanvas.height = FFT_HEIGHT
|
|
87
|
+
fftCanvasContext = fftCanvas.getContext('2d');
|
|
88
|
+
fftCanvasContext.fillStyle = "rgb(0, 0, 0)";
|
|
89
|
+
fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT);
|
|
90
|
+
|
|
91
|
+
bandwidthCanvas = document.getElementById("bandwidthCanvas");
|
|
92
|
+
bandwidthCanvas.width = BANDWIDTH_WIDTH
|
|
93
|
+
bandwidthCanvas.height = BANDWIDTH_HEIGHT
|
|
94
|
+
bandwidthCanvasContext = bandwidthCanvas.getContext('2d');
|
|
95
|
+
bandwidthCanvasContext.fillStyle = "rgb(255, 255, 255)";
|
|
96
|
+
bandwidthCanvasContext.fillRect(0, 0, BANDWIDTH_WIDTH, BANDWIDTH_HEIGHT);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function startAnalyzer() {
|
|
100
|
+
// FFT
|
|
101
|
+
if (audioElement.captureStream !== undefined) {
|
|
102
|
+
audioContext = new AudioContext();
|
|
103
|
+
audioAnalyzer = audioContext.createAnalyser();
|
|
104
|
+
audioAnalyzer.fftSize = 128;
|
|
105
|
+
audioFrequencyBinCount = audioAnalyzer.frequencyBinCount;
|
|
106
|
+
audioFrequencyData = new Uint8Array(audioFrequencyBinCount);
|
|
107
|
+
const stream = audioElement.captureStream();
|
|
108
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
109
|
+
source.connect(audioAnalyzer);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Bandwidth
|
|
113
|
+
bandwidthBinCount = BANDWIDTH_WIDTH / 2;
|
|
114
|
+
bandwidthBins = [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function setConnectionText(message) {
|
|
118
|
+
connectionText.innerText = message;
|
|
119
|
+
if (message.length == 0) {
|
|
120
|
+
connectionText.style.display = "none";
|
|
121
|
+
} else {
|
|
122
|
+
connectionText.style.display = "inline-block";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setStreamState(state) {
|
|
127
|
+
streamState = state;
|
|
128
|
+
streamStateText.innerText = streamState;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onAnimationFrame() {
|
|
132
|
+
// FFT
|
|
133
|
+
if (audioAnalyzer !== undefined) {
|
|
134
|
+
audioAnalyzer.getByteFrequencyData(audioFrequencyData);
|
|
135
|
+
fftCanvasContext.fillStyle = "rgb(0, 0, 0)";
|
|
136
|
+
fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT);
|
|
137
|
+
const barCount = audioFrequencyBinCount;
|
|
138
|
+
const barWidth = (FFT_WIDTH / audioFrequencyBinCount) - 1;
|
|
139
|
+
for (let bar = 0; bar < barCount; bar++) {
|
|
140
|
+
const barHeight = audioFrequencyData[bar];
|
|
141
|
+
fftCanvasContext.fillStyle = `rgb(${barHeight / 256 * 200 + 50}, 50, ${50 + 2 * bar})`;
|
|
142
|
+
fftCanvasContext.fillRect(bar * (barWidth + 1), FFT_HEIGHT - barHeight, barWidth, barHeight);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Bandwidth
|
|
147
|
+
bandwidthCanvasContext.fillStyle = "rgb(255, 255, 255)";
|
|
148
|
+
bandwidthCanvasContext.fillRect(0, 0, BANDWIDTH_WIDTH, BANDWIDTH_HEIGHT);
|
|
149
|
+
bandwidthCanvasContext.fillStyle = `rgb(100, 100, 100)`;
|
|
150
|
+
for (let t = 0; t < bandwidthBins.length; t++) {
|
|
151
|
+
const lineHeight = (bandwidthBins[t] / 1000) * BANDWIDTH_HEIGHT;
|
|
152
|
+
bandwidthCanvasContext.fillRect(t * 2, BANDWIDTH_HEIGHT - lineHeight, 2, lineHeight);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Display again at the next frame
|
|
156
|
+
requestAnimationFrame(onAnimationFrame);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onMediaSourceOpen() {
|
|
160
|
+
console.log(this.readyState);
|
|
161
|
+
sourceBuffer = mediaSource.addSourceBuffer("audio/aac");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function onMediaSourceClose() {
|
|
165
|
+
console.log(this.readyState);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function onMediaSourceEnd() {
|
|
169
|
+
console.log(this.readyState);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function startAudio() {
|
|
173
|
+
try {
|
|
174
|
+
console.log("starting audio...");
|
|
175
|
+
audioOnButton.disabled = true;
|
|
176
|
+
audioState = "starting";
|
|
177
|
+
await audioElement.play();
|
|
178
|
+
console.log("audio started");
|
|
179
|
+
audioState = "playing";
|
|
180
|
+
startAnalyzer();
|
|
181
|
+
} catch(error) {
|
|
182
|
+
console.error(`play failed: ${error}`);
|
|
183
|
+
audioState = "stopped";
|
|
184
|
+
audioOnButton.disabled = false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function onAudioPacket(packet) {
|
|
189
|
+
if (audioState != "stopped") {
|
|
190
|
+
// Queue the audio packet.
|
|
191
|
+
sourceBuffer.appendBuffer(packet);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
packetsReceived += 1;
|
|
195
|
+
packetsReceivedText.innerText = packetsReceived;
|
|
196
|
+
bytesReceived += packet.byteLength;
|
|
197
|
+
bytesReceivedText.innerText = bytesReceived;
|
|
198
|
+
|
|
199
|
+
bandwidthBins[bandwidthBins.length] = packet.byteLength;
|
|
200
|
+
if (bandwidthBins.length > bandwidthBinCount) {
|
|
201
|
+
bandwidthBins.shift();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function onChannelOpen() {
|
|
206
|
+
console.log('channel OPEN');
|
|
207
|
+
setConnectionText("");
|
|
208
|
+
controlsDiv.style.visibility = "visible";
|
|
209
|
+
|
|
210
|
+
// Handshake with the backend.
|
|
211
|
+
sendMessage({
|
|
212
|
+
type: "hello"
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function onChannelClose() {
|
|
217
|
+
console.log('channel CLOSED');
|
|
218
|
+
setConnectionText("Connection to CLI app closed, restart it and reload this page.");
|
|
219
|
+
controlsDiv.style.visibility = "hidden";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function onChannelError(error) {
|
|
223
|
+
console.log(`channel ERROR: ${error}`);
|
|
224
|
+
setConnectionText(`Connection to CLI app error ({${error}}), restart it and reload this page.`);
|
|
225
|
+
controlsDiv.style.visibility = "hidden";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function onChannelMessage(message) {
|
|
229
|
+
if (typeof message.data === 'string' || message.data instanceof String) {
|
|
230
|
+
// JSON message.
|
|
231
|
+
const jsonMessage = JSON.parse(message.data);
|
|
232
|
+
console.log(`channel MESSAGE: ${message.data}`);
|
|
233
|
+
|
|
234
|
+
// Dispatch the message.
|
|
235
|
+
const handlerName = `on${jsonMessage.type.charAt(0).toUpperCase()}${jsonMessage.type.slice(1)}Message`
|
|
236
|
+
const handler = messageHandlers[handlerName];
|
|
237
|
+
if (handler !== undefined) {
|
|
238
|
+
const params = jsonMessage.params;
|
|
239
|
+
if (params === undefined) {
|
|
240
|
+
params = {};
|
|
241
|
+
}
|
|
242
|
+
handler(params);
|
|
243
|
+
} else {
|
|
244
|
+
console.warn(`unhandled message: ${jsonMessage.type}`)
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
// BINARY audio data.
|
|
248
|
+
onAudioPacket(message.data);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function onHelloMessage(params) {
|
|
253
|
+
codecText.innerText = params.codec;
|
|
254
|
+
if (params.codec != "aac") {
|
|
255
|
+
audioOnButton.disabled = true;
|
|
256
|
+
audioSupportMessageText.innerText = "Only AAC can be played, audio will be disabled";
|
|
257
|
+
audioSupportMessageText.style.display = "inline-block";
|
|
258
|
+
} else {
|
|
259
|
+
audioSupportMessageText.innerText = "";
|
|
260
|
+
audioSupportMessageText.style.display = "none";
|
|
261
|
+
}
|
|
262
|
+
if (params.streamState) {
|
|
263
|
+
setStreamState(params.streamState);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function onStartMessage(params) {
|
|
268
|
+
setStreamState("STARTED");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function onStopMessage(params) {
|
|
272
|
+
setStreamState("STOPPED");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function onSuspendMessage(params) {
|
|
276
|
+
setStreamState("SUSPENDED");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function onConnectionMessage(params) {
|
|
280
|
+
connectionStateText.innerText = `CONNECTED: ${params.peer_name} (${params.peer_address})`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function onDisconnectionMessage(params) {
|
|
284
|
+
connectionStateText.innerText = "DISCONNECTED";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function sendMessage(message) {
|
|
288
|
+
channelSocket.send(JSON.stringify(message));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function connect() {
|
|
292
|
+
console.log("connecting to CLI app");
|
|
293
|
+
|
|
294
|
+
channelSocket = new WebSocket(channelUrl);
|
|
295
|
+
channelSocket.binaryType = "arraybuffer";
|
|
296
|
+
channelSocket.onopen = onChannelOpen;
|
|
297
|
+
channelSocket.onclose = onChannelClose;
|
|
298
|
+
channelSocket.onerror = onChannelError;
|
|
299
|
+
channelSocket.onmessage = onChannelMessage;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const messageHandlers = {
|
|
303
|
+
onHelloMessage,
|
|
304
|
+
onStartMessage,
|
|
305
|
+
onStopMessage,
|
|
306
|
+
onSuspendMessage,
|
|
307
|
+
onConnectionMessage,
|
|
308
|
+
onDisconnectionMessage
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
window.onload = (event) => {
|
|
312
|
+
init();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
}());
|