mirror of
https://github.com/google/bumble.git
synced 2026-05-10 04:18:03 +00:00
wip
This commit is contained in:
@@ -41,17 +41,27 @@ body, h1, h2, h3, h4, h5, h6 {
|
|||||||
margin: 6px;
|
margin: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#connectionStateText {
|
||||||
|
background-color: rgb(112, 146, 206);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
#propertiesTable {
|
#propertiesTable {
|
||||||
border: grey;
|
border: grey;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
|
margin-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
padding-left: 8px;
|
padding-left: 6px;
|
||||||
padding-right: 8px;
|
padding-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.properties td:nth-child(even) {
|
.properties td:nth-child(even) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<span id="streamStateText">IDLE</span>
|
<span id="streamStateText">IDLE</span>
|
||||||
|
<span id="connectionStateText">NOT CONNECTED</span>
|
||||||
<div id="controlsDiv">
|
<div id="controlsDiv">
|
||||||
<button id="audioOnButton">Audio On</button>
|
<button id="audioOnButton">Audio On</button>
|
||||||
<span id="audioSupportMessageText"></span>
|
<span id="audioSupportMessageText"></span>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ let codecText;
|
|||||||
let packetsReceivedText;
|
let packetsReceivedText;
|
||||||
let bytesReceivedText;
|
let bytesReceivedText;
|
||||||
let streamStateText;
|
let streamStateText;
|
||||||
|
let connectionStateText;
|
||||||
let controlsDiv;
|
let controlsDiv;
|
||||||
let audioOnButton;
|
let audioOnButton;
|
||||||
let mediaSource;
|
let mediaSource;
|
||||||
@@ -27,7 +28,7 @@ let fftCanvasContext;
|
|||||||
let bandwidthCanvas;
|
let bandwidthCanvas;
|
||||||
let bandwidthCanvasContext;
|
let bandwidthCanvasContext;
|
||||||
let bandwidthBinCount;
|
let bandwidthBinCount;
|
||||||
let bandwidthBins;
|
let bandwidthBins = [];
|
||||||
|
|
||||||
const FFT_WIDTH = 800;
|
const FFT_WIDTH = 800;
|
||||||
const FFT_HEIGHT = 256;
|
const FFT_HEIGHT = 256;
|
||||||
@@ -56,11 +57,14 @@ function initUI() {
|
|||||||
packetsReceivedText = document.getElementById("packetsReceivedText");
|
packetsReceivedText = document.getElementById("packetsReceivedText");
|
||||||
bytesReceivedText = document.getElementById("bytesReceivedText");
|
bytesReceivedText = document.getElementById("bytesReceivedText");
|
||||||
streamStateText = document.getElementById("streamStateText");
|
streamStateText = document.getElementById("streamStateText");
|
||||||
|
connectionStateText = document.getElementById("connectionStateText");
|
||||||
audioSupportMessageText = document.getElementById("audioSupportMessageText");
|
audioSupportMessageText = document.getElementById("audioSupportMessageText");
|
||||||
|
|
||||||
audioOnButton.onclick = () => startAudio();
|
audioOnButton.onclick = () => startAudio();
|
||||||
|
|
||||||
setConnectionText("");
|
setConnectionText("");
|
||||||
|
|
||||||
|
requestAnimationFrame(onAnimationFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initMediaSource() {
|
function initMediaSource() {
|
||||||
@@ -94,20 +98,20 @@ function initAnalyzer() {
|
|||||||
|
|
||||||
function startAnalyzer() {
|
function startAnalyzer() {
|
||||||
// FFT
|
// FFT
|
||||||
audioContext = new AudioContext();
|
if (audioElement.captureStream !== undefined) {
|
||||||
audioAnalyzer = audioContext.createAnalyser();
|
audioContext = new AudioContext();
|
||||||
audioAnalyzer.fftSize = 128;
|
audioAnalyzer = audioContext.createAnalyser();
|
||||||
audioFrequencyBinCount = audioAnalyzer.frequencyBinCount;
|
audioAnalyzer.fftSize = 128;
|
||||||
audioFrequencyData = new Uint8Array(audioFrequencyBinCount);
|
audioFrequencyBinCount = audioAnalyzer.frequencyBinCount;
|
||||||
const stream = audioElement.captureStream();
|
audioFrequencyData = new Uint8Array(audioFrequencyBinCount);
|
||||||
const source = audioContext.createMediaStreamSource(stream);
|
const stream = audioElement.captureStream();
|
||||||
source.connect(audioAnalyzer);
|
const source = audioContext.createMediaStreamSource(stream);
|
||||||
|
source.connect(audioAnalyzer);
|
||||||
|
}
|
||||||
|
|
||||||
// Bandwidth
|
// Bandwidth
|
||||||
bandwidthBinCount = BANDWIDTH_WIDTH / 2;
|
bandwidthBinCount = BANDWIDTH_WIDTH / 2;
|
||||||
bandwidthBins = [];
|
bandwidthBins = [];
|
||||||
|
|
||||||
requestAnimationFrame(onAnimationFrame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConnectionText(message) {
|
function setConnectionText(message) {
|
||||||
@@ -121,15 +125,17 @@ function setConnectionText(message) {
|
|||||||
|
|
||||||
function onAnimationFrame() {
|
function onAnimationFrame() {
|
||||||
// FFT
|
// FFT
|
||||||
audioAnalyzer.getByteFrequencyData(audioFrequencyData);
|
if (audioAnalyzer !== undefined) {
|
||||||
fftCanvasContext.fillStyle = "rgb(0, 0, 0)";
|
audioAnalyzer.getByteFrequencyData(audioFrequencyData);
|
||||||
fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT);
|
fftCanvasContext.fillStyle = "rgb(0, 0, 0)";
|
||||||
const barCount = audioFrequencyBinCount;
|
fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT);
|
||||||
const barWidth = (FFT_WIDTH / audioFrequencyBinCount) - 1;
|
const barCount = audioFrequencyBinCount;
|
||||||
for (let bar = 0; bar < barCount; bar++) {
|
const barWidth = (FFT_WIDTH / audioFrequencyBinCount) - 1;
|
||||||
const barHeight = audioFrequencyData[bar];
|
for (let bar = 0; bar < barCount; bar++) {
|
||||||
fftCanvasContext.fillStyle = `rgb(${barHeight / 256 * 200 + 50}, 50, ${50 + 2 * bar})`;
|
const barHeight = audioFrequencyData[bar];
|
||||||
fftCanvasContext.fillRect(bar * (barWidth + 1), FFT_HEIGHT - barHeight, barWidth, barHeight);
|
fftCanvasContext.fillStyle = `rgb(${barHeight / 256 * 200 + 50}, 50, ${50 + 2 * bar})`;
|
||||||
|
fftCanvasContext.fillRect(bar * (barWidth + 1), FFT_HEIGHT - barHeight, barWidth, barHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bandwidth
|
// Bandwidth
|
||||||
@@ -175,14 +181,11 @@ async function startAudio() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onAudioPacket(packet) {
|
function onAudioPacket(packet) {
|
||||||
if (audioState == "stopped") {
|
if (audioState != "stopped") {
|
||||||
// Drop the packet, we're not ready to play.
|
// Queue the audio packet.
|
||||||
return;
|
sourceBuffer.appendBuffer(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue the audio packet.
|
|
||||||
sourceBuffer.appendBuffer(packet);
|
|
||||||
|
|
||||||
packetsReceived += 1;
|
packetsReceived += 1;
|
||||||
packetsReceivedText.innerText = packetsReceived;
|
packetsReceivedText.innerText = packetsReceived;
|
||||||
bytesReceived += packet.byteLength;
|
bytesReceived += packet.byteLength;
|
||||||
@@ -268,6 +271,14 @@ function onSuspendMessage(params) {
|
|||||||
streamStateText.innerText = streamState;
|
streamStateText.innerText = streamState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onConnectionMessage(params) {
|
||||||
|
connectionStateText.innerText = `CONNECTED: ${params.peer_name} (${params.peer_address})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDisconnectionMessage(params) {
|
||||||
|
connectionStateText.innerText = "DISCONNECTED";
|
||||||
|
}
|
||||||
|
|
||||||
function sendMessage(message) {
|
function sendMessage(message) {
|
||||||
channelSocket.send(JSON.stringify(message));
|
channelSocket.send(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
@@ -287,7 +298,9 @@ const messageHandlers = {
|
|||||||
onHelloMessage,
|
onHelloMessage,
|
||||||
onStartMessage,
|
onStartMessage,
|
||||||
onStopMessage,
|
onStopMessage,
|
||||||
onSuspendMessage
|
onSuspendMessage,
|
||||||
|
onConnectionMessage,
|
||||||
|
onDisconnectionMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = (event) => {
|
window.onload = (event) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright 2021-2022 Google LLC
|
# Copyright 2021-2023 Google LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -23,6 +23,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import subprocess
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ from aiohttp import web
|
|||||||
import bumble
|
import bumble
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
from bumble.core import BT_BR_EDR_TRANSPORT
|
from bumble.core import BT_BR_EDR_TRANSPORT
|
||||||
from bumble.device import Device, DeviceConfiguration
|
from bumble.device import Connection, Device, DeviceConfiguration, Peer
|
||||||
from bumble.sdp import ServiceAttribute
|
from bumble.sdp import ServiceAttribute
|
||||||
from bumble.transport import open_transport
|
from bumble.transport import open_transport
|
||||||
from bumble.avdtp import (
|
from bumble.avdtp import (
|
||||||
@@ -61,6 +62,12 @@ from bumble.utils import AsyncRunner
|
|||||||
from bumble.codecs import AacAudioRtpPacket
|
from bumble.codecs import AacAudioRtpPacket
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Logging
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Constants
|
# Constants
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -100,13 +107,22 @@ class SbcAudioExtractor:
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Output:
|
class Output:
|
||||||
async def start(self):
|
async def start(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def suspend(self):
|
async def suspend(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_connection(self, connection: Connection) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_disconnection(self, reason: int) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_rtp_packet(self, packet: MediaPacket) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -157,7 +173,7 @@ class QueuedOutput(Output):
|
|||||||
|
|
||||||
def on_rtp_packet(self, packet: MediaPacket) -> None:
|
def on_rtp_packet(self, packet: MediaPacket) -> None:
|
||||||
if self.packets.qsize() > self.MAX_QUEUE_SIZE:
|
if self.packets.qsize() > self.MAX_QUEUE_SIZE:
|
||||||
print("queue full, dropping")
|
logger.debug("queue full, dropping")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.packets.put_nowait(self.extractor.extract_audio(packet))
|
self.packets.put_nowait(self.extractor.extract_audio(packet))
|
||||||
@@ -170,6 +186,19 @@ class WebSocketOutput(QueuedOutput):
|
|||||||
self.send_audio = send_audio
|
self.send_audio = send_audio
|
||||||
self.send_message = send_message
|
self.send_message = send_message
|
||||||
|
|
||||||
|
async def on_connection(self, connection: Connection) -> None:
|
||||||
|
await connection.request_remote_name()
|
||||||
|
peer_name = '' if connection.peer_name is None else connection.peer_name
|
||||||
|
peer_address = str(connection.peer_address).replace('/P', '')
|
||||||
|
await self.send_message(
|
||||||
|
'connection',
|
||||||
|
peer_address=peer_address,
|
||||||
|
peer_name=peer_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_disconnection(self, reason) -> None:
|
||||||
|
await self.send_message('disconnection')
|
||||||
|
|
||||||
async def on_audio_packet(self, packet: bytes) -> None:
|
async def on_audio_packet(self, packet: bytes) -> None:
|
||||||
await self.send_audio(packet)
|
await self.send_audio(packet)
|
||||||
|
|
||||||
@@ -202,7 +231,7 @@ class FfplayOutput(QueuedOutput):
|
|||||||
if self.started:
|
if self.started:
|
||||||
return
|
return
|
||||||
|
|
||||||
super().start()
|
await super().start()
|
||||||
|
|
||||||
self.subprocess = await asyncio.create_subprocess_shell(
|
self.subprocess = await asyncio.create_subprocess_shell(
|
||||||
'ffplay -acodec aac pipe:0',
|
'ffplay -acodec aac pipe:0',
|
||||||
@@ -225,7 +254,7 @@ class FfplayOutput(QueuedOutput):
|
|||||||
async def read_stream(name, stream):
|
async def read_stream(name, stream):
|
||||||
while True:
|
while True:
|
||||||
data = await stream.read()
|
data = await stream.read()
|
||||||
print(f'{name}:', data)
|
logger.debug(f'{name}:', data)
|
||||||
|
|
||||||
await asyncio.wait(
|
await asyncio.wait(
|
||||||
[
|
[
|
||||||
@@ -238,13 +267,13 @@ class FfplayOutput(QueuedOutput):
|
|||||||
asyncio.create_task(self.subprocess.wait()),
|
asyncio.create_task(self.subprocess.wait()),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
print("FFPLAY done")
|
logger.debug("FFPLAY done")
|
||||||
|
|
||||||
async def on_audio_packet(self, packet):
|
async def on_audio_packet(self, packet):
|
||||||
try:
|
try:
|
||||||
self.subprocess.stdin.write(packet)
|
self.subprocess.stdin.write(packet)
|
||||||
except Exception:
|
except Exception:
|
||||||
print('!!!! exception while sending audio to ffplay pipe')
|
logger.warning('!!!! exception while sending audio to ffplay pipe')
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -307,13 +336,15 @@ class UiServer:
|
|||||||
self.channel_socket = ws
|
self.channel_socket = ws
|
||||||
async for message in ws:
|
async for message in ws:
|
||||||
if message.type == aiohttp.WSMsgType.TEXT:
|
if message.type == aiohttp.WSMsgType.TEXT:
|
||||||
print(f'<<< received message: {message.data}')
|
logger.debug(f'<<< received message: {message.data}')
|
||||||
await self.on_message(message.data)
|
await self.on_message(message.data)
|
||||||
elif message.type == aiohttp.WSMsgType.ERROR:
|
elif message.type == aiohttp.WSMsgType.ERROR:
|
||||||
print(f'channel connection closed with exception {ws.exception()}')
|
logger.debug(
|
||||||
|
f'channel connection closed with exception {ws.exception()}'
|
||||||
|
)
|
||||||
|
|
||||||
self.channel_socket = None
|
self.channel_socket = None
|
||||||
print('--- channel connection closed')
|
logger.debug('--- channel connection closed')
|
||||||
|
|
||||||
return ws
|
return ws
|
||||||
|
|
||||||
@@ -329,10 +360,16 @@ class UiServer:
|
|||||||
await handler(**message_params)
|
await handler(**message_params)
|
||||||
|
|
||||||
async def on_hello_message(self):
|
async def on_hello_message(self):
|
||||||
print('HELLO')
|
logger.debug('HELLO')
|
||||||
await self.send_message(
|
await self.send_message(
|
||||||
'hello', bumble_version=bumble.__version__, codec=self.speaker().codec
|
'hello', bumble_version=bumble.__version__, codec=self.speaker().codec
|
||||||
)
|
)
|
||||||
|
if connection := self.speaker().connection:
|
||||||
|
await self.send_message(
|
||||||
|
'connection',
|
||||||
|
peer_address=str(connection.peer_address).replace('/P', ''),
|
||||||
|
peer_name=connection.peer_name,
|
||||||
|
)
|
||||||
|
|
||||||
async def send_message(self, message_type: str, **kwargs) -> None:
|
async def send_message(self, message_type: str, **kwargs) -> None:
|
||||||
if self.channel_socket is None:
|
if self.channel_socket is None:
|
||||||
@@ -345,7 +382,10 @@ class UiServer:
|
|||||||
if self.channel_socket is None:
|
if self.channel_socket is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.channel_socket.send_bytes(data)
|
try:
|
||||||
|
await self.channel_socket.send_bytes(data)
|
||||||
|
except Exception as error:
|
||||||
|
logger.warning(f'exception while sending audio packet: {error}')
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -356,6 +396,7 @@ class Speaker:
|
|||||||
self.discover = discover
|
self.discover = discover
|
||||||
self.ui_port = ui_port
|
self.ui_port = ui_port
|
||||||
self.device = None
|
self.device = None
|
||||||
|
self.connection = None
|
||||||
self.listener = None
|
self.listener = None
|
||||||
self.packets_received = 0
|
self.packets_received = 0
|
||||||
self.bytes_received = 0
|
self.bytes_received = 0
|
||||||
@@ -424,16 +465,28 @@ class Speaker:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def dispatch_to_outputs(self, function):
|
||||||
|
for output in self.outputs:
|
||||||
|
await function(output)
|
||||||
|
|
||||||
def on_bluetooth_connection(self, connection):
|
def on_bluetooth_connection(self, connection):
|
||||||
print(f"Connection: {connection}")
|
print(f'Connection: {connection}')
|
||||||
|
self.connection = connection
|
||||||
connection.on('disconnection', self.on_bluetooth_disconnection)
|
connection.on('disconnection', self.on_bluetooth_disconnection)
|
||||||
|
AsyncRunner.spawn(
|
||||||
|
self.dispatch_to_outputs(lambda output: output.on_connection(connection))
|
||||||
|
)
|
||||||
|
|
||||||
def on_bluetooth_disconnection(self, reason):
|
def on_bluetooth_disconnection(self, reason):
|
||||||
print(f"Disconnection ({reason})")
|
print(f'Disconnection ({reason})')
|
||||||
|
self.connection = None
|
||||||
AsyncRunner.spawn(self.advertise())
|
AsyncRunner.spawn(self.advertise())
|
||||||
|
AsyncRunner.spawn(
|
||||||
|
self.dispatch_to_outputs(lambda output: output.on_disconnection(reason))
|
||||||
|
)
|
||||||
|
|
||||||
def on_avdtp_connection(self, protocol):
|
def on_avdtp_connection(self, protocol):
|
||||||
print("Audio Stream Open")
|
print('Audio Stream Open')
|
||||||
|
|
||||||
# Add a sink endpoint to the server
|
# Add a sink endpoint to the server
|
||||||
sink = protocol.add_sink(self.codec_capabilities())
|
sink = protocol.add_sink(self.codec_capabilities())
|
||||||
@@ -456,19 +509,16 @@ class Speaker:
|
|||||||
print("Audio Stream Closed")
|
print("Audio Stream Closed")
|
||||||
|
|
||||||
def on_sink_start(self):
|
def on_sink_start(self):
|
||||||
print("Sink Start")
|
print("Sink Started\u001b[0K")
|
||||||
for output in self.outputs:
|
AsyncRunner.spawn(self.dispatch_to_outputs(lambda output: output.start()))
|
||||||
AsyncRunner.spawn(output.start())
|
|
||||||
|
|
||||||
def on_sink_stop(self):
|
def on_sink_stop(self):
|
||||||
print("Sink Stop")
|
print("Sink Stopped\u001b[0K")
|
||||||
for output in self.outputs:
|
AsyncRunner.spawn(self.dispatch_to_outputs(lambda output: output.stop()))
|
||||||
AsyncRunner.spawn(output.stop())
|
|
||||||
|
|
||||||
def on_sink_suspend(self):
|
def on_sink_suspend(self):
|
||||||
print("Sink Suspend")
|
print("Sink Suspended\u001b[0K")
|
||||||
for output in self.outputs:
|
AsyncRunner.spawn(self.dispatch_to_outputs(lambda output: output.suspend()))
|
||||||
AsyncRunner.spawn(output.suspend())
|
|
||||||
|
|
||||||
def on_sink_configuration(self, config):
|
def on_sink_configuration(self, config):
|
||||||
print("Sink Configuration:")
|
print("Sink Configuration:")
|
||||||
@@ -625,6 +675,16 @@ def play(ctx, transport, codec, connect_address, discover, output, ui_port):
|
|||||||
)
|
)
|
||||||
output = list(filter(lambda x: x != '@ffplay', output))
|
output = list(filter(lambda x: x != '@ffplay', output))
|
||||||
|
|
||||||
|
if '@ffplay' in output:
|
||||||
|
# Check if ffplay is installed
|
||||||
|
try:
|
||||||
|
subprocess.run(['ffplay', '-version'], capture_output=True, check=True)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(
|
||||||
|
color('ffplay not installed, @ffplay output will be disabled', 'yellow')
|
||||||
|
)
|
||||||
|
output = list(filter(lambda x: x != '@ffplay', output))
|
||||||
|
|
||||||
asyncio.run(
|
asyncio.run(
|
||||||
Speaker(transport, codec, discover, output, ui_port).run(connect_address)
|
Speaker(transport, codec, discover, output, ui_port).run(connect_address)
|
||||||
)
|
)
|
||||||
@@ -632,7 +692,7 @@ def play(ctx, transport, codec, connect_address, discover, output, ui_port):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def main():
|
def main():
|
||||||
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper())
|
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
|
||||||
speaker_cli()
|
speaker_cli()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ from .hci import (
|
|||||||
HCI_Read_Local_Version_Information_Command,
|
HCI_Read_Local_Version_Information_Command,
|
||||||
HCI_Reset_Command,
|
HCI_Reset_Command,
|
||||||
HCI_Set_Event_Mask_Command,
|
HCI_Set_Event_Mask_Command,
|
||||||
|
map_null_terminated_utf8_string,
|
||||||
)
|
)
|
||||||
from .core import (
|
from .core import (
|
||||||
BT_BR_EDR_TRANSPORT,
|
BT_BR_EDR_TRANSPORT,
|
||||||
@@ -887,7 +888,12 @@ class Host(AbortableEventEmitter):
|
|||||||
if event.status != HCI_SUCCESS:
|
if event.status != HCI_SUCCESS:
|
||||||
self.emit('remote_name_failure', event.bd_addr, event.status)
|
self.emit('remote_name_failure', event.bd_addr, event.status)
|
||||||
else:
|
else:
|
||||||
self.emit('remote_name', event.bd_addr, event.remote_name)
|
utf8_name = event.remote_name
|
||||||
|
terminator = utf8_name.find(0)
|
||||||
|
if terminator >= 0:
|
||||||
|
utf8_name = utf8_name[0:terminator]
|
||||||
|
|
||||||
|
self.emit('remote_name', event.bd_addr, utf8_name)
|
||||||
|
|
||||||
def on_hci_remote_host_supported_features_notification_event(self, event):
|
def on_hci_remote_host_supported_features_notification_event(self, event):
|
||||||
self.emit(
|
self.emit(
|
||||||
|
|||||||
Reference in New Issue
Block a user