bumble 0.0.155__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 CHANGED
@@ -1,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '0.0.155'
4
- __version_tuple__ = version_tuple = (0, 0, 155)
3
+ __version__ = version = '0.0.156'
4
+ __version_tuple__ = version_tuple = (0, 0, 156)
bumble/a2dp.py CHANGED
@@ -432,6 +432,7 @@ class AacMediaCodecInformation(
432
432
  cls.SAMPLING_FREQUENCY_BITS[x] for x in sampling_frequencies
433
433
  ),
434
434
  channels=sum(cls.CHANNELS_BITS[x] for x in channels),
435
+ rfa=0,
435
436
  vbr=vbr,
436
437
  bitrate=bitrate,
437
438
  )
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
+ }());