[Incomplete] Zeroconf feature
This commit is contained in:
parent
5768f49f4a
commit
5a2f8eed5d
|
@ -4,6 +4,8 @@ import concurrent.futures
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf
|
||||||
|
|
||||||
from librespot.common import Utils
|
from librespot.common import Utils
|
||||||
from librespot.core import Session
|
from librespot.core import Session
|
||||||
from librespot.crypto import DiffieHellman
|
from librespot.crypto import DiffieHellman
|
||||||
|
@ -13,6 +15,7 @@ from librespot.standard import Runnable
|
||||||
|
|
||||||
|
|
||||||
class ZeroconfServer(Closeable):
|
class ZeroconfServer(Closeable):
|
||||||
|
SERVICE = "spotify-connect"
|
||||||
__MAX_PORT = 65536
|
__MAX_PORT = 65536
|
||||||
__MIN_PORT = 1024
|
__MIN_PORT = 1024
|
||||||
__EOL = "\r\n"
|
__EOL = "\r\n"
|
||||||
|
|
|
@ -35,9 +35,9 @@ class Player(Closeable, PlayerSession.Listener, AudioSink.Listener):
|
||||||
self._events = Player.EventsDispatcher(conf)
|
self._events = Player.EventsDispatcher(conf)
|
||||||
self._sink = AudioSink(conf, self)
|
self._sink = AudioSink(conf, self)
|
||||||
|
|
||||||
self._init_state()
|
self.__init_state()
|
||||||
|
|
||||||
def _init_state(self):
|
def __init_state(self):
|
||||||
self._state = StateWrapper.StateWrapper(self._session, self,
|
self._state = StateWrapper.StateWrapper(self._session, self,
|
||||||
self._conf)
|
self._conf)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
class Packet:
|
||||||
|
__FLAG_RESPONSE: int = 15
|
||||||
|
__FLAG_AA: int = 10
|
||||||
|
__questions: list
|
||||||
|
__answers: list
|
||||||
|
__authorities: list
|
||||||
|
__additionals: list
|
||||||
|
__id: int
|
||||||
|
__flags: int
|
||||||
|
__address: str
|
||||||
|
|
||||||
|
def __init__(self, _id: int):
|
||||||
|
self.__id = _id
|
||||||
|
self.__questions = []
|
||||||
|
self.__answers = []
|
||||||
|
self.__authorities = []
|
||||||
|
self.__additionals = []
|
||||||
|
|
||||||
|
def get_address(self) -> str:
|
||||||
|
return self.__address
|
||||||
|
|
||||||
|
def set_address(self, address: str) -> None:
|
||||||
|
self.__address = address
|
||||||
|
|
||||||
|
def get_id(self) -> int:
|
||||||
|
return self.__id
|
||||||
|
|
||||||
|
def is_response(self) -> bool:
|
||||||
|
return self.__is_flag(self.__FLAG_RESPONSE)
|
||||||
|
|
||||||
|
def set_response(self, on: bool) -> None:
|
||||||
|
self.__set_flag(self.__FLAG_RESPONSE, on)
|
||||||
|
|
||||||
|
def is_authoritative(self) -> bool:
|
||||||
|
return self.__is_flag(self.__FLAG_AA)
|
||||||
|
|
||||||
|
def set_authoritative(self, on: bool) -> None:
|
||||||
|
self.__set_flag(self.__FLAG_AA, on)
|
||||||
|
|
||||||
|
def __is_flag(self, flag: int):
|
||||||
|
return (self.__flags & (1 << flag)) != 0
|
||||||
|
|
||||||
|
def __set_flag(self, flag: int, on: bool):
|
||||||
|
if on:
|
||||||
|
self.__flags |= (1 << flag)
|
||||||
|
else:
|
||||||
|
self.__flags &= ~(1 << flag)
|
||||||
|
|
||||||
|
def read(self, inp: bytes, address: str):
|
||||||
|
self.__address = address
|
||||||
|
self.__id = struct.unpack("<h", inp[0:2])[0]
|
||||||
|
self.__flags = struct.unpack("<h", inp[2:4])[0]
|
||||||
|
num_questions = struct.unpack("<h", inp[4:6])[0]
|
||||||
|
num_answers = struct.unpack("<h", inp[6:8])[0]
|
||||||
|
num_authorities = struct.unpack("<h", inp[8:10])[0]
|
||||||
|
num_additionals = struct.unpack("<h", inp[10:12])[0]
|
|
@ -0,0 +1,38 @@
|
||||||
|
from librespot.zeroconf import Packet
|
||||||
|
|
||||||
|
|
||||||
|
class Record:
|
||||||
|
TYPE_A: int = 0x01
|
||||||
|
TYPE_PTR: int = 0x0c
|
||||||
|
TYPE_CNAME: int = 0x05
|
||||||
|
TYPE_TXT: int = 0x10
|
||||||
|
TYPE_AAAA: int = 0x1c
|
||||||
|
TYPE_SRV: int = 0x21
|
||||||
|
TYPE_NSEC: int = 0x2f
|
||||||
|
TYPE_ANY: int = 0xff
|
||||||
|
__type: int
|
||||||
|
_ttl: int
|
||||||
|
__name: str
|
||||||
|
__clazz: int
|
||||||
|
__data: bytes
|
||||||
|
|
||||||
|
def __init__(self, typ: int):
|
||||||
|
self.__type = typ
|
||||||
|
self.__clazz = 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _write_name(self, name: str, packet: Packet):
|
||||||
|
length = len(name)
|
||||||
|
out = b""
|
||||||
|
start = 0
|
||||||
|
for i in range(length + 1):
|
||||||
|
c = "." if i == length else name[i]
|
||||||
|
if c == ".":
|
||||||
|
out += bytes([i - start])
|
||||||
|
for j in range(start, i):
|
||||||
|
out += name.encode()[j]
|
||||||
|
start = i + 1
|
||||||
|
out += bytes([0])
|
||||||
|
return out, len(name) + 2
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from librespot.zeroconf import Packet
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
__alias: str
|
||||||
|
__service: str
|
||||||
|
__port: int
|
||||||
|
__text: dict
|
||||||
|
__domain: str
|
||||||
|
__protocol: str
|
||||||
|
__host: str
|
||||||
|
|
||||||
|
def __init__(self, alias: str, service: str, port: int):
|
||||||
|
self.__alias = alias
|
||||||
|
for s in alias:
|
||||||
|
c = ord(s)
|
||||||
|
if c < 0x20 or c == 0x7f:
|
||||||
|
raise TypeError()
|
||||||
|
|
||||||
|
self.__service = service
|
||||||
|
self.__port = port
|
||||||
|
self.__protocol = "tcp"
|
||||||
|
self.__text = {}
|
||||||
|
|
||||||
|
def __esc(self, text: str):
|
||||||
|
ns = ""
|
||||||
|
for s in text:
|
||||||
|
c = ord(s)
|
||||||
|
if c == 0x2e or c == 0x5c:
|
||||||
|
ns += "\\"
|
||||||
|
ns += s
|
||||||
|
return ns
|
||||||
|
|
||||||
|
def set_protocol(self, protocol: str) -> Service:
|
||||||
|
if protocol == "tcp" or protocol == "udp":
|
||||||
|
self.__protocol = protocol
|
||||||
|
else:
|
||||||
|
raise TypeError()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_domain(self) -> str:
|
||||||
|
return self.__domain
|
||||||
|
|
||||||
|
def set_domain(self, domain: str) -> Service:
|
||||||
|
if domain is None or len(domain) < 2 or domain[0] != ".":
|
||||||
|
raise TypeError(domain)
|
||||||
|
self.__domain = domain
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_host(self) -> str:
|
||||||
|
return self.__host
|
||||||
|
|
||||||
|
def set_host(self, host: str) -> Service:
|
||||||
|
self.__host = host
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_packet(self) -> Packet:
|
||||||
|
packet = Packet()
|
||||||
|
return packet
|
|
@ -0,0 +1,64 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from librespot.standard import Closeable
|
||||||
|
import base64
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
class Zeroconf(Closeable):
|
||||||
|
__DISCOVERY = "_services._dns-sd._udp.local"
|
||||||
|
__BROADCAST4: socket.socket
|
||||||
|
__BROADCAST6: socket.socket
|
||||||
|
__use_ipv4: bool = True
|
||||||
|
__use_ipv6: bool = True
|
||||||
|
__hostname: str
|
||||||
|
__domain: str
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
try:
|
||||||
|
self.__BROADCAST4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.__BROADCAST4.connect(("224.0.0.251", 5353))
|
||||||
|
self.__BROADCAST6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
|
self.__BROADCAST6.connect(("FF02::FB", 5353))
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
self.set_domain(".local")
|
||||||
|
self.set_local_host_name(Zeroconf.get_or_create_local_host_name())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_or_create_local_host_name() -> str:
|
||||||
|
host = socket.gethostname()
|
||||||
|
if host == "localhost":
|
||||||
|
host = base64.b64encode(random.randint(-9223372036854775808, 9223372036854775807)).decode() + ".local"
|
||||||
|
return host
|
||||||
|
|
||||||
|
def set_use_ipv4(self, ipv4: bool) -> Zeroconf:
|
||||||
|
self.__use_ipv4 = ipv4
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_use_ipv6(self, ipv6: bool) -> Zeroconf:
|
||||||
|
self.__use_ipv6 = ipv6
|
||||||
|
return self
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
super().close()
|
||||||
|
|
||||||
|
def get_domain(self) -> str:
|
||||||
|
return self.__domain
|
||||||
|
|
||||||
|
def set_domain(self, domain: str) -> Zeroconf:
|
||||||
|
self.__domain = domain
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_local_host_name(self) -> str:
|
||||||
|
return self.__hostname
|
||||||
|
|
||||||
|
def set_local_host_name(self, name: str) -> Zeroconf:
|
||||||
|
self.__hostname = name
|
||||||
|
return self
|
||||||
|
|
||||||
|
def handle_packet(self, packet):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def announce(self, service):
|
||||||
|
pass
|
|
@ -0,0 +1 @@
|
||||||
|
from librespot.zeroconf.Packet import Packet
|
|
@ -1,4 +1,4 @@
|
||||||
defusedxml==0.7.1
|
defusedxml==0.7.1
|
||||||
protobuf==3.15.8
|
protobuf==3.15.8
|
||||||
pycryptodome==3.10.1
|
pycryptodome==3.10.1
|
||||||
requests==2.25.1
|
requests==2.25.1
|
Loading…
Reference in New Issue