[Incomplete] Zeroconf feature
This commit is contained in:
parent
5768f49f4a
commit
5a2f8eed5d
|
@ -4,6 +4,8 @@ import concurrent.futures
|
|||
import random
|
||||
import socket
|
||||
|
||||
from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf
|
||||
|
||||
from librespot.common import Utils
|
||||
from librespot.core import Session
|
||||
from librespot.crypto import DiffieHellman
|
||||
|
@ -13,6 +15,7 @@ from librespot.standard import Runnable
|
|||
|
||||
|
||||
class ZeroconfServer(Closeable):
|
||||
SERVICE = "spotify-connect"
|
||||
__MAX_PORT = 65536
|
||||
__MIN_PORT = 1024
|
||||
__EOL = "\r\n"
|
||||
|
|
|
@ -35,9 +35,9 @@ class Player(Closeable, PlayerSession.Listener, AudioSink.Listener):
|
|||
self._events = Player.EventsDispatcher(conf)
|
||||
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._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
|
||||
protobuf==3.15.8
|
||||
pycryptodome==3.10.1
|
||||
requests==2.25.1
|
||||
requests==2.25.1
|
Loading…
Reference in New Issue