From 275c3b553952560909cb29492138732f95590dc2 Mon Sep 17 00:00:00 2001
From: KiriChen-Wind <1841938040@qq.com>
Date: Wed, 22 Apr 2026 19:16:26 +0800
Subject: [PATCH 01/83] v0.1.3-alpha
---
miair/dlna/templates.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/miair/dlna/templates.py b/miair/dlna/templates.py
index 9b8bf8b..cf3ccf9 100644
--- a/miair/dlna/templates.py
+++ b/miair/dlna/templates.py
@@ -24,7 +24,7 @@ def device_description_xml(udn: str, friendly_name: str, base_url: str) -> str:
{DEVICE_TYPE}
{escape(friendly_name)}
MiAir
- https://github.com/miair
+ https://github.com/KiriChen-Wind/MiAir
MiAir - Xiaomi Speaker DLNA Audio Renderer
MiAir Speaker
1.0
From 5fe79ae5e41c3150353d3e9ec39331867cf8ac15 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Wed, 22 Apr 2026 19:19:30 +0800
Subject: [PATCH 02/83] Add files via upload
---
miair/dlna/templates.py | 1720 +++++++++++++++++++--------------------
1 file changed, 860 insertions(+), 860 deletions(-)
diff --git a/miair/dlna/templates.py b/miair/dlna/templates.py
index 9b8bf8b..25a68ca 100644
--- a/miair/dlna/templates.py
+++ b/miair/dlna/templates.py
@@ -1,860 +1,860 @@
-"""UPnP/DLNA XML 模板"""
-
-from xml.sax.saxutils import escape
-
-from miair.const import (
- AVTRANSPORT_URN,
- CONNECTION_MANAGER_URN,
- DEVICE_TYPE,
- RENDERING_CONTROL_URN,
- SUPPORTED_PROTOCOLS,
-)
-
-
-def device_description_xml(udn: str, friendly_name: str, base_url: str) -> str:
- """生成设备描述 XML (参照 MaCast)"""
- return f"""
-
-
- 1
- 0
-
-
- {DEVICE_TYPE}
- {escape(friendly_name)}
- MiAir
- https://github.com/miair
- MiAir - Xiaomi Speaker DLNA Audio Renderer
- MiAir Speaker
- 1.0
- 1
- uuid:{udn}
- DMR-1.50
- audio-only
-
-
- {AVTRANSPORT_URN}
- urn:upnp-org:serviceId:AVTransport
- /device/{udn}/AVTransport.xml
- /device/{udn}/AVTransport/control
- /device/{udn}/AVTransport/event
-
-
- {RENDERING_CONTROL_URN}
- urn:upnp-org:serviceId:RenderingControl
- /device/{udn}/RenderingControl.xml
- /device/{udn}/RenderingControl/control
- /device/{udn}/RenderingControl/event
-
-
- {CONNECTION_MANAGER_URN}
- urn:upnp-org:serviceId:ConnectionManager
- /device/{udn}/ConnectionManager.xml
- /device/{udn}/ConnectionManager/control
- /device/{udn}/ConnectionManager/event
-
-
-
-"""
-
-
-# AVTransport SCPD (参照 MaCast 声明 14 个动作)
-AVTRANSPORT_SCPD = """
-
-
- 1
- 0
-
-
-
- SetAVTransportURI
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- CurrentURI
- in
- AVTransportURI
-
-
- CurrentURIMetaData
- in
- AVTransportURIMetaData
-
-
-
-
- SetNextAVTransportURI
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- NextURI
- in
- NextAVTransportURI
-
-
- NextURIMetaData
- in
- NextAVTransportURIMetaData
-
-
-
-
- Play
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Speed
- in
- TransportPlaySpeed
-
-
-
-
- Pause
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
-
-
- Stop
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
-
-
- Seek
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Unit
- in
- A_ARG_TYPE_SeekMode
-
-
- Target
- in
- A_ARG_TYPE_SeekTarget
-
-
-
-
- Next
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
-
-
- Previous
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
-
-
- SetPlayMode
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- NewPlayMode
- in
- CurrentPlayMode
-
-
-
-
- GetTransportInfo
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- CurrentTransportState
- out
- TransportState
-
-
- CurrentTransportStatus
- out
- TransportStatus
-
-
- CurrentSpeed
- out
- TransportPlaySpeed
-
-
-
-
- GetPositionInfo
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Track
- out
- CurrentTrack
-
-
- TrackDuration
- out
- CurrentTrackDuration
-
-
- TrackMetaData
- out
- CurrentTrackMetaData
-
-
- TrackURI
- out
- CurrentTrackURI
-
-
- RelTime
- out
- RelativeTimePosition
-
-
- AbsTime
- out
- AbsoluteTimePosition
-
-
- RelCount
- out
- RelativeCounterPosition
-
-
- AbsCount
- out
- AbsoluteCounterPosition
-
-
-
-
- GetMediaInfo
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- NrTracks
- out
- NumberOfTracks
-
-
- MediaDuration
- out
- CurrentMediaDuration
-
-
- CurrentURI
- out
- AVTransportURI
-
-
- CurrentURIMetaData
- out
- AVTransportURIMetaData
-
-
- NextURI
- out
- NextAVTransportURI
-
-
- NextURIMetaData
- out
- NextAVTransportURIMetaData
-
-
- PlayMedium
- out
- PlaybackStorageMedium
-
-
- RecordMedium
- out
- RecordStorageMedium
-
-
- WriteStatus
- out
- RecordMediumWriteStatus
-
-
-
-
- GetTransportSettings
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- PlayMode
- out
- CurrentPlayMode
-
-
- RecQualityMode
- out
- CurrentRecordQualityMode
-
-
-
-
- GetDeviceCapabilities
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- PlayMedia
- out
- PossiblePlaybackStorageMedia
-
-
- RecMedia
- out
- PossibleRecordStorageMedia
-
-
- RecQualityModes
- out
- PossibleRecordQualityModes
-
-
-
-
- GetCurrentTransportActions
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Actions
- out
- CurrentTransportActions
-
-
-
-
-
-
- A_ARG_TYPE_InstanceID
- ui4
-
-
- TransportState
- string
-
- STOPPED
- PLAYING
- PAUSED_PLAYBACK
- TRANSITIONING
- NO_MEDIA_PRESENT
-
-
-
- TransportStatus
- string
-
- OK
- ERROR_OCCURRED
-
-
-
- TransportPlaySpeed
- string
-
-
- AVTransportURI
- string
-
-
- AVTransportURIMetaData
- string
-
-
- CurrentTrack
- ui4
-
-
- CurrentTrackDuration
- string
-
-
- CurrentTrackMetaData
- string
-
-
- CurrentTrackURI
- string
-
-
- RelativeTimePosition
- string
-
-
- AbsoluteTimePosition
- string
-
-
- RelativeCounterPosition
- i4
-
-
- AbsoluteCounterPosition
- i4
-
-
- CurrentPlayMode
- string
-
- NORMAL
- REPEAT_ONE
- REPEAT_ALL
- SHUFFLE
-
-
-
- CurrentRecordQualityMode
- string
-
-
- NumberOfTracks
- ui4
-
-
- CurrentMediaDuration
- string
-
-
- NextAVTransportURI
- string
-
-
- NextAVTransportURIMetaData
- string
-
-
- PlaybackStorageMedium
- string
-
-
- RecordStorageMedium
- string
-
-
- RecordMediumWriteStatus
- string
-
-
- PossiblePlaybackStorageMedia
- string
-
-
- PossibleRecordStorageMedia
- string
-
-
- PossibleRecordQualityModes
- string
-
-
- A_ARG_TYPE_SeekMode
- string
-
- REL_TIME
- TRACK_NR
-
-
-
- A_ARG_TYPE_SeekTarget
- string
-
-
- CurrentTransportActions
- string
-
-
- LastChange
- string
-
-
-"""
-
-
-# RenderingControl SCPD (参照 MaCast 声明 6 个动作)
-RENDERING_CONTROL_SCPD = """
-
-
- 1
- 0
-
-
-
- GetVolume
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Channel
- in
- A_ARG_TYPE_Channel
-
-
- CurrentVolume
- out
- Volume
-
-
-
-
- SetVolume
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Channel
- in
- A_ARG_TYPE_Channel
-
-
- DesiredVolume
- in
- Volume
-
-
-
-
- GetMute
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Channel
- in
- A_ARG_TYPE_Channel
-
-
- CurrentMute
- out
- Mute
-
-
-
-
- SetMute
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- Channel
- in
- A_ARG_TYPE_Channel
-
-
- DesiredMute
- in
- Mute
-
-
-
-
- ListPresets
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- CurrentPresetNameList
- out
- PresetNameList
-
-
-
-
- SelectPreset
-
-
- InstanceID
- in
- A_ARG_TYPE_InstanceID
-
-
- PresetName
- in
- A_ARG_TYPE_PresetName
-
-
-
-
-
-
- A_ARG_TYPE_InstanceID
- ui4
-
-
- A_ARG_TYPE_Channel
- string
-
- Master
-
-
-
- A_ARG_TYPE_PresetName
- string
-
-
- PresetNameList
- string
-
-
- Volume
- ui2
-
- 0
- 100
- 1
-
-
-
- Mute
- boolean
-
-
- LastChange
- string
-
-
-"""
-
-
-CONNECTION_MANAGER_SCPD = f"""
-
-
- 1
- 0
-
-
-
- GetProtocolInfo
-
-
- Source
- out
- SourceProtocolInfo
-
-
- Sink
- out
- SinkProtocolInfo
-
-
-
-
- GetCurrentConnectionIDs
-
-
- ConnectionIDs
- out
- CurrentConnectionIDs
-
-
-
-
- GetCurrentConnectionInfo
-
-
- ConnectionID
- in
- A_ARG_TYPE_ConnectionID
-
-
- RcsID
- out
- A_ARG_TYPE_RcsID
-
-
- AVTransportID
- out
- A_ARG_TYPE_AVTransportID
-
-
- ProtocolInfo
- out
- A_ARG_TYPE_ProtocolInfo
-
-
- PeerConnectionManager
- out
- A_ARG_TYPE_ConnectionManager
-
-
- PeerConnectionID
- out
- A_ARG_TYPE_ConnectionID
-
-
- Direction
- out
- A_ARG_TYPE_Direction
-
-
- Status
- out
- A_ARG_TYPE_ConnectionStatus
-
-
-
-
-
-
- A_ARG_TYPE_ConnectionID
- i4
-
-
- A_ARG_TYPE_RcsID
- i4
-
-
- A_ARG_TYPE_AVTransportID
- i4
-
-
- A_ARG_TYPE_ProtocolInfo
- string
-
-
- A_ARG_TYPE_ConnectionManager
- string
-
-
- A_ARG_TYPE_Direction
- string
-
-
- A_ARG_TYPE_ConnectionStatus
- string
-
- OK
- ContentFormatMismatch
- InsufficientBandwidth
- UnreliableChannel
- Unknown
-
-
-
- SourceProtocolInfo
- string
-
-
- SinkProtocolInfo
- string
-
-
- CurrentConnectionIDs
- string
-
-
-"""
-
-
-def soap_response(service_urn: str, action: str, params: dict[str, str]) -> str:
- """生成 SOAP 响应 XML (正确转义所有参数值)"""
- params_xml = ""
- for key, value in params.items():
- params_xml += f" <{key}>{escape(str(value))}{key}>\n"
-
- return f"""
-
-
-
-{params_xml}
-
-"""
-
-
-def soap_fault(error_code: int, error_description: str) -> str:
- """生成 SOAP 错误响应 XML"""
- return f"""
-
-
-
- s:Client
- UPnPError
-
-
- {error_code}
- {escape(str(error_description))}
-
-
-
-
-"""
+"""UPnP/DLNA XML 模板"""
+
+from xml.sax.saxutils import escape
+
+from miair.const import (
+ AVTRANSPORT_URN,
+ CONNECTION_MANAGER_URN,
+ DEVICE_TYPE,
+ RENDERING_CONTROL_URN,
+ SUPPORTED_PROTOCOLS,
+)
+
+
+def device_description_xml(udn: str, friendly_name: str, base_url: str) -> str:
+ """生成设备描述 XML (参照 MaCast)"""
+ return f"""
+
+
+ 1
+ 0
+
+
+ {DEVICE_TYPE}
+ {escape(friendly_name)}
+ MiAir
+ https://github.com/KiriChen-Wind/MiAir
+ MiAir - Xiaomi Speaker DLNA Audio Renderer
+ MiAir Speaker
+ 1.0
+ 1
+ uuid:{udn}
+ DMR-1.50
+ audio-only
+
+
+ {AVTRANSPORT_URN}
+ urn:upnp-org:serviceId:AVTransport
+ /device/{udn}/AVTransport.xml
+ /device/{udn}/AVTransport/control
+ /device/{udn}/AVTransport/event
+
+
+ {RENDERING_CONTROL_URN}
+ urn:upnp-org:serviceId:RenderingControl
+ /device/{udn}/RenderingControl.xml
+ /device/{udn}/RenderingControl/control
+ /device/{udn}/RenderingControl/event
+
+
+ {CONNECTION_MANAGER_URN}
+ urn:upnp-org:serviceId:ConnectionManager
+ /device/{udn}/ConnectionManager.xml
+ /device/{udn}/ConnectionManager/control
+ /device/{udn}/ConnectionManager/event
+
+
+
+"""
+
+
+# AVTransport SCPD (参照 MaCast 声明 14 个动作)
+AVTRANSPORT_SCPD = """
+
+
+ 1
+ 0
+
+
+
+ SetAVTransportURI
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ CurrentURI
+ in
+ AVTransportURI
+
+
+ CurrentURIMetaData
+ in
+ AVTransportURIMetaData
+
+
+
+
+ SetNextAVTransportURI
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ NextURI
+ in
+ NextAVTransportURI
+
+
+ NextURIMetaData
+ in
+ NextAVTransportURIMetaData
+
+
+
+
+ Play
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Speed
+ in
+ TransportPlaySpeed
+
+
+
+
+ Pause
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+
+
+ Stop
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+
+
+ Seek
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Unit
+ in
+ A_ARG_TYPE_SeekMode
+
+
+ Target
+ in
+ A_ARG_TYPE_SeekTarget
+
+
+
+
+ Next
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+
+
+ Previous
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+
+
+ SetPlayMode
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ NewPlayMode
+ in
+ CurrentPlayMode
+
+
+
+
+ GetTransportInfo
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ CurrentTransportState
+ out
+ TransportState
+
+
+ CurrentTransportStatus
+ out
+ TransportStatus
+
+
+ CurrentSpeed
+ out
+ TransportPlaySpeed
+
+
+
+
+ GetPositionInfo
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Track
+ out
+ CurrentTrack
+
+
+ TrackDuration
+ out
+ CurrentTrackDuration
+
+
+ TrackMetaData
+ out
+ CurrentTrackMetaData
+
+
+ TrackURI
+ out
+ CurrentTrackURI
+
+
+ RelTime
+ out
+ RelativeTimePosition
+
+
+ AbsTime
+ out
+ AbsoluteTimePosition
+
+
+ RelCount
+ out
+ RelativeCounterPosition
+
+
+ AbsCount
+ out
+ AbsoluteCounterPosition
+
+
+
+
+ GetMediaInfo
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ NrTracks
+ out
+ NumberOfTracks
+
+
+ MediaDuration
+ out
+ CurrentMediaDuration
+
+
+ CurrentURI
+ out
+ AVTransportURI
+
+
+ CurrentURIMetaData
+ out
+ AVTransportURIMetaData
+
+
+ NextURI
+ out
+ NextAVTransportURI
+
+
+ NextURIMetaData
+ out
+ NextAVTransportURIMetaData
+
+
+ PlayMedium
+ out
+ PlaybackStorageMedium
+
+
+ RecordMedium
+ out
+ RecordStorageMedium
+
+
+ WriteStatus
+ out
+ RecordMediumWriteStatus
+
+
+
+
+ GetTransportSettings
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ PlayMode
+ out
+ CurrentPlayMode
+
+
+ RecQualityMode
+ out
+ CurrentRecordQualityMode
+
+
+
+
+ GetDeviceCapabilities
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ PlayMedia
+ out
+ PossiblePlaybackStorageMedia
+
+
+ RecMedia
+ out
+ PossibleRecordStorageMedia
+
+
+ RecQualityModes
+ out
+ PossibleRecordQualityModes
+
+
+
+
+ GetCurrentTransportActions
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Actions
+ out
+ CurrentTransportActions
+
+
+
+
+
+
+ A_ARG_TYPE_InstanceID
+ ui4
+
+
+ TransportState
+ string
+
+ STOPPED
+ PLAYING
+ PAUSED_PLAYBACK
+ TRANSITIONING
+ NO_MEDIA_PRESENT
+
+
+
+ TransportStatus
+ string
+
+ OK
+ ERROR_OCCURRED
+
+
+
+ TransportPlaySpeed
+ string
+
+
+ AVTransportURI
+ string
+
+
+ AVTransportURIMetaData
+ string
+
+
+ CurrentTrack
+ ui4
+
+
+ CurrentTrackDuration
+ string
+
+
+ CurrentTrackMetaData
+ string
+
+
+ CurrentTrackURI
+ string
+
+
+ RelativeTimePosition
+ string
+
+
+ AbsoluteTimePosition
+ string
+
+
+ RelativeCounterPosition
+ i4
+
+
+ AbsoluteCounterPosition
+ i4
+
+
+ CurrentPlayMode
+ string
+
+ NORMAL
+ REPEAT_ONE
+ REPEAT_ALL
+ SHUFFLE
+
+
+
+ CurrentRecordQualityMode
+ string
+
+
+ NumberOfTracks
+ ui4
+
+
+ CurrentMediaDuration
+ string
+
+
+ NextAVTransportURI
+ string
+
+
+ NextAVTransportURIMetaData
+ string
+
+
+ PlaybackStorageMedium
+ string
+
+
+ RecordStorageMedium
+ string
+
+
+ RecordMediumWriteStatus
+ string
+
+
+ PossiblePlaybackStorageMedia
+ string
+
+
+ PossibleRecordStorageMedia
+ string
+
+
+ PossibleRecordQualityModes
+ string
+
+
+ A_ARG_TYPE_SeekMode
+ string
+
+ REL_TIME
+ TRACK_NR
+
+
+
+ A_ARG_TYPE_SeekTarget
+ string
+
+
+ CurrentTransportActions
+ string
+
+
+ LastChange
+ string
+
+
+"""
+
+
+# RenderingControl SCPD (参照 MaCast 声明 6 个动作)
+RENDERING_CONTROL_SCPD = """
+
+
+ 1
+ 0
+
+
+
+ GetVolume
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Channel
+ in
+ A_ARG_TYPE_Channel
+
+
+ CurrentVolume
+ out
+ Volume
+
+
+
+
+ SetVolume
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Channel
+ in
+ A_ARG_TYPE_Channel
+
+
+ DesiredVolume
+ in
+ Volume
+
+
+
+
+ GetMute
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Channel
+ in
+ A_ARG_TYPE_Channel
+
+
+ CurrentMute
+ out
+ Mute
+
+
+
+
+ SetMute
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ Channel
+ in
+ A_ARG_TYPE_Channel
+
+
+ DesiredMute
+ in
+ Mute
+
+
+
+
+ ListPresets
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ CurrentPresetNameList
+ out
+ PresetNameList
+
+
+
+
+ SelectPreset
+
+
+ InstanceID
+ in
+ A_ARG_TYPE_InstanceID
+
+
+ PresetName
+ in
+ A_ARG_TYPE_PresetName
+
+
+
+
+
+
+ A_ARG_TYPE_InstanceID
+ ui4
+
+
+ A_ARG_TYPE_Channel
+ string
+
+ Master
+
+
+
+ A_ARG_TYPE_PresetName
+ string
+
+
+ PresetNameList
+ string
+
+
+ Volume
+ ui2
+
+ 0
+ 100
+ 1
+
+
+
+ Mute
+ boolean
+
+
+ LastChange
+ string
+
+
+"""
+
+
+CONNECTION_MANAGER_SCPD = f"""
+
+
+ 1
+ 0
+
+
+
+ GetProtocolInfo
+
+
+ Source
+ out
+ SourceProtocolInfo
+
+
+ Sink
+ out
+ SinkProtocolInfo
+
+
+
+
+ GetCurrentConnectionIDs
+
+
+ ConnectionIDs
+ out
+ CurrentConnectionIDs
+
+
+
+
+ GetCurrentConnectionInfo
+
+
+ ConnectionID
+ in
+ A_ARG_TYPE_ConnectionID
+
+
+ RcsID
+ out
+ A_ARG_TYPE_RcsID
+
+
+ AVTransportID
+ out
+ A_ARG_TYPE_AVTransportID
+
+
+ ProtocolInfo
+ out
+ A_ARG_TYPE_ProtocolInfo
+
+
+ PeerConnectionManager
+ out
+ A_ARG_TYPE_ConnectionManager
+
+
+ PeerConnectionID
+ out
+ A_ARG_TYPE_ConnectionID
+
+
+ Direction
+ out
+ A_ARG_TYPE_Direction
+
+
+ Status
+ out
+ A_ARG_TYPE_ConnectionStatus
+
+
+
+
+
+
+ A_ARG_TYPE_ConnectionID
+ i4
+
+
+ A_ARG_TYPE_RcsID
+ i4
+
+
+ A_ARG_TYPE_AVTransportID
+ i4
+
+
+ A_ARG_TYPE_ProtocolInfo
+ string
+
+
+ A_ARG_TYPE_ConnectionManager
+ string
+
+
+ A_ARG_TYPE_Direction
+ string
+
+
+ A_ARG_TYPE_ConnectionStatus
+ string
+
+ OK
+ ContentFormatMismatch
+ InsufficientBandwidth
+ UnreliableChannel
+ Unknown
+
+
+
+ SourceProtocolInfo
+ string
+
+
+ SinkProtocolInfo
+ string
+
+
+ CurrentConnectionIDs
+ string
+
+
+"""
+
+
+def soap_response(service_urn: str, action: str, params: dict[str, str]) -> str:
+ """生成 SOAP 响应 XML (正确转义所有参数值)"""
+ params_xml = ""
+ for key, value in params.items():
+ params_xml += f" <{key}>{escape(str(value))}{key}>\n"
+
+ return f"""
+
+
+
+{params_xml}
+
+"""
+
+
+def soap_fault(error_code: int, error_description: str) -> str:
+ """生成 SOAP 错误响应 XML"""
+ return f"""
+
+
+
+ s:Client
+ UPnPError
+
+
+ {error_code}
+ {escape(str(error_description))}
+
+
+
+
+"""
From 8f3ec62453f862ee2b62692fb2a25dbf070d3fc1 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Wed, 22 Apr 2026 21:02:54 +0800
Subject: [PATCH 03/83] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 249d114..71382f3 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,10 @@ python miair.py
```
程序将自动安装相关依赖库,请确保网络畅通
+## 我们
+**[QQ交流群&测试版本发布]https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info")**
-### 后续 可能 添加的功能
+## 后续 可能 添加的功能
- 支持 Docker 部署
- 支持 OpenWrt 部署
From fc854fb74fdb2e22b07d22fe4a45566e1a20ba84 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Wed, 22 Apr 2026 21:03:56 +0800
Subject: [PATCH 04/83] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 71382f3..dde79fe 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ python miair.py
程序将自动安装相关依赖库,请确保网络畅通
## 我们
-**[QQ交流群&测试版本发布]https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info")**
+**[QQ交流群&测试版本发布]https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "QQ交流群&测试版本发布")**
## 后续 可能 添加的功能
From abc4a6894c3d5db6393758ddc4ee12317cecd464 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Wed, 22 Apr 2026 21:05:07 +0800
Subject: [PATCH 05/83] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index dde79fe..a1c42f7 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ python miair.py
程序将自动安装相关依赖库,请确保网络畅通
## 我们
-**[QQ交流群&测试版本发布]https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "QQ交流群&测试版本发布")**
+**[交流&测试版本发布](https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "交流&测试版本发布")**
## 后续 可能 添加的功能
@@ -25,4 +25,4 @@ python miair.py
- ......
-[](https://raw.githubusercontent.com/KiriChen-Wind/MiAir/main/preview.png "preview")
\ No newline at end of file
+[](https://raw.githubusercontent.com/KiriChen-Wind/MiAir/main/preview.png "preview")
From 4893ad08ad842345bdcd5a18866631ab814f267e Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 03:00:54 +0800
Subject: [PATCH 06/83] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index a1c42f7..69bd87c 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ python miair.py
程序将自动安装相关依赖库,请确保网络畅通
## 我们
-**[交流&测试版本发布](https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "交流&测试版本发布")**
+**[需要帮助&交流&测试版本发布](https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "需要帮助&交流&测试版本发布")**
## 后续 可能 添加的功能
From 0ed5bd774f042359be0965eeb46a18c0617fbd31 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:35:59 +0800
Subject: [PATCH 07/83] Update README.md
---
README.md | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 69bd87c..0b9af14 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
**[XiaoMusic](https://github.com/hanxi/xiaomusic "XiaoMusic")** **[AirPlay2 Receiver](https://github.com/openairplay/airplay2-receiver "AirPlay2 Receiver")** **[MaCast](https://github.com/xfangfang/Macast "MaCast")**
## 快速开始
-
+### Windows
*确保设备已安装 Python 3.12+*
进入项目目录,使用终端执行
@@ -14,6 +14,25 @@ python miair.py
```
程序将自动安装相关依赖库,请确保网络畅通
+### Docker (Thanks @SyunSS)
+```bash
+# 安装 Git
+opkg update
+opkg install git
+opkg install git-http
+
+# 克隆项目
+git clone -b docker https://github.com/KiriChen-Wind/MiAir.git
+cd MiAir
+
+# 运行安装脚本,按提示输入小米账号密码即可
+chmod +x deploy.sh manage.sh
+./deploy.sh
+```
+
+安装完成后访问 `http://容器宿主机IP:8300` 打开 Web 管理界面。
+请确保容器网络为Host。
+
## 我们
**[需要帮助&交流&测试版本发布](https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "需要帮助&交流&测试版本发布")**
From ecd1ae59c55166e163c02dfaab02337af2677bae Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:36:38 +0800
Subject: [PATCH 08/83] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0b9af14..852e7a2 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ chmod +x deploy.sh manage.sh
## 后续 可能 添加的功能
-- 支持 Docker 部署
+- ~~支持 Docker 部署~~
- 支持 OpenWrt 部署
- ~~支持 MacOS 部署~~
- ......
From 86e426046e3d3e6eff7f69150cb46c00e6dfe06d Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:40:03 +0800
Subject: [PATCH 09/83] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 852e7a2..a6f6ac2 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ opkg install git-http
git clone -b docker https://github.com/KiriChen-Wind/MiAir.git
cd MiAir
-# 运行安装脚本,按提示输入小米账号密码即可
+# 赋予权限并运行安装脚本
chmod +x deploy.sh manage.sh
./deploy.sh
```
From a63a432ca7e539d0b7e4e27d18659aeb04ebda58 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:44:37 +0800
Subject: [PATCH 10/83] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index a6f6ac2..3a0ee78 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,13 @@
### Windows
*确保设备已安装 Python 3.12+*
+前往Release页面下载最新版本
进入项目目录,使用终端执行
```python
python miair.py
```
程序将自动安装相关依赖库,请确保网络畅通
+安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
### Docker (Thanks @SyunSS)
```bash
@@ -30,7 +32,7 @@ chmod +x deploy.sh manage.sh
./deploy.sh
```
-安装完成后访问 `http://容器宿主机IP:8300` 打开 Web 管理界面。
+安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
请确保容器网络为Host。
## 我们
From 39666fe090c759cdaf888a82aa52ae949fb75027 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:46:08 +0800
Subject: [PATCH 11/83] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3a0ee78..836aaf2 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
```python
python miair.py
```
-程序将自动安装相关依赖库,请确保网络畅通
+程序将自动安装相关依赖库,请确保网络畅通\
安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
### Docker (Thanks @SyunSS)
From 579fe275340a9f873ac11c78ad10cf12b52e17c3 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:46:59 +0800
Subject: [PATCH 12/83] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 836aaf2..f7adabd 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
### Windows
*确保设备已安装 Python 3.12+*
-前往Release页面下载最新版本
+前往Release页面下载最新版本\
进入项目目录,使用终端执行
```python
python miair.py
From 9ac6872cdd8b1265974a1fa4f2e1835c7e742ece Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 15:10:23 +0800
Subject: [PATCH 13/83] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f7adabd..4229284 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,8 @@ chmod +x deploy.sh manage.sh
```
安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
-请确保容器网络为Host。
+请确保容器网络为Host。\
+部分情况下,修改配置后容器可能无法自动重启,请手动重启容器。
## 我们
**[需要帮助&交流&测试版本发布](https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "需要帮助&交流&测试版本发布")**
From b6c86a97b8d7868c472363058262953866359874 Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 15:41:23 +0800
Subject: [PATCH 14/83] Update README.md
---
README.md | 39 ++++++++++++++++++++++++++++++++++++---
1 file changed, 36 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 4229284..3da3103 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
**[XiaoMusic](https://github.com/hanxi/xiaomusic "XiaoMusic")** **[AirPlay2 Receiver](https://github.com/openairplay/airplay2-receiver "AirPlay2 Receiver")** **[MaCast](https://github.com/xfangfang/Macast "MaCast")**
## 快速开始
-### Windows
+## Windows
*确保设备已安装 Python 3.12+*
前往Release页面下载最新版本\
@@ -16,7 +16,11 @@ python miair.py
程序将自动安装相关依赖库,请确保网络畅通\
安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
-### Docker (Thanks @SyunSS)
+## Docker (Thanks @SyunSS)
+
+支持平台:Linux / OpenWrt / macOS
+
+### 使用脚本部署
```bash
# 安装 Git
opkg update
@@ -36,12 +40,41 @@ chmod +x deploy.sh manage.sh
请确保容器网络为Host。\
部分情况下,修改配置后容器可能无法自动重启,请手动重启容器。
+### 在线镜像部署
+```bash
+docker run -d \
+ --name miair \
+ --network=host \
+ -e MIAIR_HOSTNAME=你的局域网IP \
+ ghcr.io/syunss/miair:latest
+```
+
+> **示例:** `MIAIR_HOSTNAME=192.168.31.1`
+
+### 本地构建部署
+
+```bash
+git clone -b docker https://github.com/SyunSS/MiAir.git
+cd MiAir
+docker build -t miair .
+docker run -d --name miair --network=host -e MIAIR_HOSTNAME=你的IP miair
+```
+
+### Docker 相关命令
+```bash
+docker logs -f miair # 查看日志
+docker stop miair # 停止
+docker start miair # 启动
+docker restart miair # 重启
+```
+
+
## 我们
**[需要帮助&交流&测试版本发布](https://qun.qq.com/universal-share/share?ac=1&authKey=1zXhx2zxgw9GG2mkecypT9clD7q0B3W3l4K0D4fQirmpDWakz0Oy2BI3ocDrgzbh&busi_data=eyJncm91cENvZGUiOiI3NDEyNjcyOTgiLCJ0b2tlbiI6InYwbitXQTF5cE9MaUJCR0hMUk03OWV0WkFoMThxbjJRaWI4dHVlbUpGdW5OdEZBVEpXMXF0T1dQUnRmRXRzYVgiLCJ1aW4iOiIxODQxOTM4MDQwIn0%3D&data=_OrA-eASJMwYwx-Uj-BReC1Xh3zGAdkn8CQskbEsQ5S66bhqvvO6dJ-QrSlRl-Ks00l5XDw1FANE8Um0w5yB8Q&svctype=4&tempid=h5_group_info "需要帮助&交流&测试版本发布")**
## 后续 可能 添加的功能
-- ~~支持 Docker 部署~~
+- ~~支持 Docker 部署~~ ✅ 已支持
- 支持 OpenWrt 部署
- ~~支持 MacOS 部署~~
- ......
From 9d8a2f5a21a6ac64d840aee9b82f699197e24a4e Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 15:43:17 +0800
Subject: [PATCH 15/83] Update README.md
---
README.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 3da3103..4da7114 100644
--- a/README.md
+++ b/README.md
@@ -36,10 +36,6 @@ chmod +x deploy.sh manage.sh
./deploy.sh
```
-安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
-请确保容器网络为Host。\
-部分情况下,修改配置后容器可能无法自动重启,请手动重启容器。
-
### 在线镜像部署
```bash
docker run -d \
@@ -59,6 +55,9 @@ cd MiAir
docker build -t miair .
docker run -d --name miair --network=host -e MIAIR_HOSTNAME=你的IP miair
```
+安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
+请确保容器网络为Host。\
+部分情况下,修改配置后容器可能无法自动重启,请手动重启容器。
### Docker 相关命令
```bash
From 04f4bfcc8508deff08993d108aa6fdfc8526f2cd Mon Sep 17 00:00:00 2001
From: KiriChen <137511967+KiriChen-Wind@users.noreply.github.com>
Date: Thu, 23 Apr 2026 15:44:38 +0800
Subject: [PATCH 16/83] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4da7114..b82c000 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
python miair.py
```
程序将自动安装相关依赖库,请确保网络畅通\
-安装完成后访问 `http://容器宿主机IP:8300` 即可打开 Web 管理界面。
+安装完成后访问 `http://主机IP:8300` 即可打开 Web 管理界面。
## Docker (Thanks @SyunSS)
From a7ff84873193d85a0f861ddfcc07c57768d1bd5c Mon Sep 17 00:00:00 2001
From: KiriChen-Wind <1841938040@qq.com>
Date: Mon, 27 Apr 2026 00:36:29 +0800
Subject: [PATCH 17/83] Update project files
---
README.md | 26 ---------
miair/airplay/utils.py | 2 +-
miair/app.py | 34 ++++++++++++
miair/auth.py | 8 ++-
miair/speaker.py | 107 ++++++++++++++++++++++++++++++++++++
miair/web/api.py | 23 +++++++-
miair/web/static/index.html | 93 ++++++++++++++++++++-----------
preview.png | Bin 179823 -> 0 bytes
pyproject.toml | 2 +-
9 files changed, 232 insertions(+), 63 deletions(-)
delete mode 100644 README.md
delete mode 100644 preview.png
diff --git a/README.md b/README.md
deleted file mode 100644
index 249d114..0000000
--- a/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# MiAir - 为小爱音箱添加 DLNA 与 AirPlay 支持
-
-## 引用以下开源项目代码 由衷感谢
-
-**[XiaoMusic](https://github.com/hanxi/xiaomusic "XiaoMusic")** **[AirPlay2 Receiver](https://github.com/openairplay/airplay2-receiver "AirPlay2 Receiver")** **[MaCast](https://github.com/xfangfang/Macast "MaCast")**
-
-## 快速开始
-
-*确保设备已安装 Python 3.12+*
-
-进入项目目录,使用终端执行
-```python
-python miair.py
-```
-程序将自动安装相关依赖库,请确保网络畅通
-
-
-### 后续 可能 添加的功能
-
-- 支持 Docker 部署
-- 支持 OpenWrt 部署
-- ~~支持 MacOS 部署~~
-- ......
-
-
-[](https://raw.githubusercontent.com/KiriChen-Wind/MiAir/main/preview.png "preview")
\ No newline at end of file
diff --git a/miair/airplay/utils.py b/miair/airplay/utils.py
index 74c7be8..25ca58f 100644
--- a/miair/airplay/utils.py
+++ b/miair/airplay/utils.py
@@ -192,7 +192,7 @@ def get_volume():
vol = interpolate(pct, 0, 100, -30, 0)
elif subsys == "Linux":
line_pct = subprocess.check_output(["amixer", "get", "Master"]).splitlines()[-1]
- m = re.search(b"\[([0-9]+)%\]", line_pct)
+ m = re.search(rb"\[([0-9]+)%\]", line_pct)
if m:
pct = int(m.group(1))
if pct < 45:
diff --git a/miair/app.py b/miair/app.py
index 9445425..66da3d8 100644
--- a/miair/app.py
+++ b/miair/app.py
@@ -86,10 +86,34 @@ async def _start_dlna_services(self):
# 登录小米
await self.auth.login()
+ # 检查登录状态
+ if not self.auth.is_logged_in():
+ log.warning("登录失败,无法启动 DLNA 服务")
+ # 清空渲染器和控制器,避免显示旧设备
+ self.renderers.clear()
+ self._did_to_udn.clear()
+ if hasattr(self, 'speaker_manager'):
+ self.speaker_manager.controllers.clear()
+ return
+
+ # 获取设备列表,确保能正常获取新账号的设备
+ device_list = await self.auth.get_device_list()
+ if not device_list:
+ log.warning("未获取到设备列表,无法启动 DLNA 服务")
+ # 清空渲染器和控制器,避免显示旧设备
+ self.renderers.clear()
+ self._did_to_udn.clear()
+ if hasattr(self, 'speaker_manager'):
+ self.speaker_manager.controllers.clear()
+ return
+
# 初始化音箱
await self.speaker_manager.init_speakers()
if not self.speaker_manager.controllers:
log.warning("没有可用的音箱,请检查配置或重新选择设备")
+ # 清空渲染器和控制器,避免显示旧设备
+ self.renderers.clear()
+ self._did_to_udn.clear()
return
# 为每个音箱创建 DLNA 渲染器
@@ -126,6 +150,13 @@ async def _start_dlna_services(self):
except Exception as e:
log.error(f"启动 DLNA 服务失败: {e}")
+ # 确保 dlna_running 为 False
+ self.dlna_running = False
+ # 清空渲染器和控制器,避免显示旧设备
+ self.renderers.clear()
+ self._did_to_udn.clear()
+ if hasattr(self, 'speaker_manager'):
+ self.speaker_manager.controllers.clear()
async def _start_airplay_for_speakers(self):
"""为每个音箱启动独立的 AirPlay 接收服务"""
@@ -143,6 +174,9 @@ async def restart_dlna_services(self):
"""重启 DLNA 服务 (用户通过 Web 修改配置后调用)"""
# 先停止现有服务
await self._stop_dlna_services()
+ # 关闭并重新初始化 auth,确保账号切换生效
+ await self.auth.close()
+ self.auth = AuthManager(self.config)
# 重建 speaker manager
self.speaker_manager = SpeakerManager(self.config, self.auth)
# 启动
diff --git a/miair/auth.py b/miair/auth.py
index 16890d9..e90d4ac 100644
--- a/miair/auth.py
+++ b/miair/auth.py
@@ -80,6 +80,9 @@ async def login(self):
self.config.password,
token_store=token_store,
)
+ # 确保 token 不为 None,避免后续操作出错
+ if not hasattr(self.account, 'token') or self.account.token is None:
+ self.account.token = {"deviceId": "miair_device"}
# 显式调用 login
# 如果使用 cookie 登录,跳过 login 调用,直接标记为已登录
@@ -93,6 +96,9 @@ async def login(self):
log.info("小米账号登录成功")
except Exception as e:
self._logged_in = False
+ # 确保 token 不为 None,避免后续操作出错
+ if not hasattr(self.account, 'token') or self.account.token is None:
+ self.account.token = {"deviceId": "miair_device"}
err_msg = str(e)
err_code = self._extract_error_code(err_msg)
if err_code == "87001" or "captcha" in err_msg.lower():
@@ -123,7 +129,7 @@ async def login(self):
async def ensure_login(self):
"""确保已登录,未登录则尝试登录"""
- if self.mina_service is None:
+ if self.mina_service is None or not self._logged_in:
await self.login()
@staticmethod
diff --git a/miair/speaker.py b/miair/speaker.py
index 19758ac..7fcfe8e 100644
--- a/miair/speaker.py
+++ b/miair/speaker.py
@@ -47,6 +47,25 @@ async def play_url(self, url: str) -> bool:
return ret is not None
except Exception as e:
log.error(f"play_url 失败: {e}")
+ # 检查是否是登录失败的错误
+ if "Login failed" in str(e) or "登录验证失败" in str(e):
+ log.info("检测到登录失败,尝试重新登录...")
+ # 重置登录状态并重新登录
+ self.auth._logged_in = False
+ await self.auth.login()
+ # 重新尝试播放
+ try:
+ await self.auth.ensure_login()
+ if self._should_use_music_api():
+ ret = await self.auth.mina_service.play_by_music_url(
+ self.device_id, url, audio_id=DEFAULT_AUDIO_ID
+ )
+ else:
+ ret = await self.auth.mina_service.play_by_url(self.device_id, url)
+ return ret is not None
+ except Exception as e2:
+ log.error(f"重新登录后 play_url 仍然失败: {e2}")
+ return False
return False
async def pause(self) -> bool:
@@ -58,6 +77,20 @@ async def pause(self) -> bool:
return True
except Exception as e:
log.error(f"pause 失败: {e}")
+ # 检查是否是登录失败的错误
+ if "Login failed" in str(e) or "登录验证失败" in str(e):
+ log.info("检测到登录失败,尝试重新登录...")
+ # 重置登录状态并重新登录
+ self.auth._logged_in = False
+ await self.auth.login()
+ # 重新尝试暂停
+ try:
+ await self.auth.ensure_login()
+ await self.auth.mina_service.player_pause(self.device_id)
+ return True
+ except Exception as e2:
+ log.error(f"重新登录后 pause 仍然失败: {e2}")
+ return False
return False
async def stop(self) -> bool:
@@ -72,6 +105,21 @@ async def stop(self) -> bool:
return True
except Exception as e:
log.error(f"stop 失败: {e}")
+ # 检查是否是登录失败的错误
+ if "Login failed" in str(e) or "登录验证失败" in str(e):
+ log.info("检测到登录失败,尝试重新登录...")
+ # 重置登录状态并重新登录
+ self.auth._logged_in = False
+ await self.auth.login()
+ # 重新尝试停止
+ try:
+ await self.auth.ensure_login()
+ await self.auth.mina_service.player_stop(self.device_id)
+ await self.pause()
+ return True
+ except Exception as e2:
+ log.error(f"重新登录后 stop 仍然失败: {e2}")
+ return False
return False
async def set_volume(self, volume: int) -> bool:
@@ -86,6 +134,22 @@ async def set_volume(self, volume: int) -> bool:
return True
except Exception as e:
log.error(f"set_volume 失败: {e}")
+ # 检查是否是登录失败的错误
+ if "Login failed" in str(e) or "登录验证失败" in str(e):
+ log.info("检测到登录失败,尝试重新登录...")
+ # 重置登录状态并重新登录
+ self.auth._logged_in = False
+ await self.auth.login()
+ # 重新尝试设置音量
+ try:
+ await self.auth.ensure_login()
+ await self.auth.mina_service.player_set_volume(self.device_id, volume)
+ if volume > 0:
+ self._last_volume = volume
+ return True
+ except Exception as e2:
+ log.error(f"重新登录后 set_volume 仍然失败: {e2}")
+ return False
return False
async def get_volume(self) -> int:
@@ -100,6 +164,24 @@ async def get_volume(self) -> int:
return volume
except Exception as e:
log.error(f"get_volume 失败: {e}")
+ # 检查是否是登录失败的错误
+ if "Login failed" in str(e) or "登录验证失败" in str(e):
+ log.info("检测到登录失败,尝试重新登录...")
+ # 重置登录状态并重新登录
+ self.auth._logged_in = False
+ await self.auth.login()
+ # 重新尝试获取音量
+ try:
+ await self.auth.ensure_login()
+ status = await self.auth.mina_service.player_get_status(self.device_id)
+ info = json.loads(status.get("data", {}).get("info", "{}"))
+ volume = int(info.get("volume", 0))
+ if volume > 0:
+ self._last_volume = volume
+ return volume
+ except Exception as e2:
+ log.error(f"重新登录后 get_volume 仍然失败: {e2}")
+ return self._last_volume
return self._last_volume
async def get_status(self) -> dict:
@@ -132,6 +214,31 @@ async def get_status(self) -> dict:
"volume": int(info.get("volume", 0)),
}
except Exception as e:
+ # 检查是否是登录失败的错误
+ if "Login failed" in str(e) or "登录验证失败" in str(e):
+ log.info("检测到登录失败,尝试重新登录...")
+ # 重置登录状态并重新登录
+ self.auth._logged_in = False
+ await self.auth.login()
+ # 重新尝试获取状态
+ try:
+ await self.auth.ensure_login()
+ playing_info = await self.auth.mina_service.player_get_status(
+ self.device_id
+ )
+ if playing_info.get("code") != 0:
+ raise Exception(f"Mina API Error: {playing_info}")
+ data = playing_info.get("data", {})
+ info_str = data.get("info")
+ if not info_str:
+ raise Exception(f"Mina API response missing 'info': {playing_info}")
+ info = json.loads(info_str)
+ return {
+ "status": info.get("status", 0),
+ "volume": int(info.get("volume", 0)),
+ }
+ except Exception as e2:
+ log.error(f"重新登录后 get_status 仍然失败: {e2}")
# 向上抛出异常,让调用者(如 DeviceServer 的轮询任务)捕获并忽略本次轮询
raise Exception(f"get_status 失败: {e}")
diff --git a/miair/web/api.py b/miair/web/api.py
index 58beb80..c27a825 100644
--- a/miair/web/api.py
+++ b/miair/web/api.py
@@ -14,10 +14,25 @@
log = logging.getLogger("miair")
+def _is_docker():
+ """检测是否在 Docker 容器中运行"""
+ try:
+ with open('/proc/1/cgroup', 'r') as f:
+ return 'docker' in f.read()
+ except:
+ return False
+
def _restart_process():
"""重启当前 Python 进程"""
log.info(f"重启进程: {sys.executable} {sys.argv}")
- if sys.platform == "win32":
+
+ # 检测是否在 Docker 容器中
+ if _is_docker():
+ # Docker 环境下,直接退出进程,让 Docker 容器重启
+ log.info("在 Docker 环境中,退出进程以触发容器重启")
+ import os
+ os._exit(1)
+ elif sys.platform == "win32":
# Windows 上 os.execv 行为不同,使用 subprocess 重启
import subprocess
subprocess.Popen([sys.executable] + sys.argv)
@@ -100,6 +115,12 @@ async def handle_save_setting(request):
if "auto_play_on_set_uri" in data:
config.auto_play_on_set_uri = data["auto_play_on_set_uri"]
+ # 更新端口配置
+ if "dlna_port" in data:
+ config.dlna_port = data["dlna_port"]
+ if "web_port" in data:
+ config.web_port = data["web_port"]
+
# 更新实验性功能配置
if "auto_resume_on_interrupt" in data:
config.auto_resume_on_interrupt = data["auto_resume_on_interrupt"]
diff --git a/miair/web/static/index.html b/miair/web/static/index.html
index f87987b..e6f3941 100644
--- a/miair/web/static/index.html
+++ b/miair/web/static/index.html
@@ -230,6 +230,10 @@
.delay-label { font-size: 13px; color: var(--text-secondary); }
.delay-unit { font-size: 13px; color: var(--text-muted); }
+ /* Section styles */
+ .section { margin-bottom: 20px; padding: 16px; background-color: var(--card-bg); border-radius: 8px; border: 1px solid var(--border); }
+ .section-title { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
+
/* Footer */
.footer { text-align: center; margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border); }
.footer-text { font-size: 11px; color: var(--text-muted); }
@@ -264,7 +268,7 @@
- 登录 account.xiaomi.com → F12 开发者工具 → Application → Cookies → 复制 userId 和 passToken
+ 登录 account.xiaomi.com → F12 开发者工具 → 应用程序(Application) → Cookies → 复制 userId 和 passToken
@@ -276,20 +280,34 @@
-
-
-
打断后续播
-
被语音唤醒打断后自动恢复播放
+
+
打断后续播
+
+
+
打断后续播
+
被语音唤醒打断后自动恢复播放
+
+
+
+
+ 恢复延迟
+
+ 秒
-
-
-
恢复延迟
-
-
秒
+
+
端口设置
+
+
+
+
+
+
+
+
@@ -326,7 +344,7 @@
@@ -350,7 +368,7 @@
currentSetting = data;
updateStatusBar(data);
- // 仅首次加载时填充账号和实验性设置,避免覆盖用户正在编辑的内容
+ // 仅首次加载时填充账号、端口和实验性设置,避免覆盖用户正在编辑的内容
if (!_initialLoaded) {
_initialLoaded = true;
if (data.cookie) {
@@ -359,13 +377,16 @@
if (userIdMatch) document.getElementById('input-userId').value = userIdMatch[1];
if (passTokenMatch) document.getElementById('input-passToken').value = passTokenMatch[1];
}
+ // 加载端口设置
+ if (data.dlna_port) document.getElementById('input-dlna-port').value = data.dlna_port;
+ if (data.web_port) document.getElementById('input-web-port').value = data.web_port;
loadExperimentalSettings();
}
if (data.device_list && data.device_list.length > 0) {
renderDeviceList(data.device_list, data.mi_did || '');
} else if (data.has_account && withDeviceList) {
- document.getElementById('device-list').innerHTML = '
未发现设备,请检查账号是否正确
';
+ document.getElementById('device-list').innerHTML = '
未发现设备,请检查账号密码是否正确
';
}
if (data.dlna_running) await fetchRenderers();
@@ -382,7 +403,7 @@
if (data.dlna_running) {
bar.className = 'status-bar status-running';
dot.className = 'status-dot dot-green';
- text.textContent = '服务运行中 · ' + data.renderers_count + ' 个渲染器 · ' + data.hostname + ':' + data.dlna_port;
+ text.textContent = '服务运行中 · 共 ' + data.renderers_count + ' 个渲染器正在工作';
} else if (data.has_account && data.mi_did) {
bar.className = 'status-bar status-stopped';
dot.className = 'status-dot dot-yellow';
@@ -498,23 +519,16 @@
body: JSON.stringify({ cookie }),
});
- const resp = await fetch('/api/devices');
- const data = await resp.json();
-
- if (data.error) {
- tip.textContent = data.error;
- tip.className = 'tip error';
- } else if (data.devices && data.devices.length > 0) {
- tip.textContent = '登录成功,发现 ' + data.devices.length + ' 个设备';
- tip.className = 'tip success';
- renderDeviceList(data.devices, currentSetting.mi_did || '');
- } else {
- tip.textContent = '未发现可用设备,请检查账号是否正确';
- tip.className = 'tip error';
- }
+ // 保存 cookie 后服务器会重启,等待后刷新页面
+ tip.textContent = '正在登录...';
+ tip.className = 'tip success';
+ // 等待服务重启
+ setTimeout(() => location.reload(), 4000);
} catch (e) {
- tip.textContent = '请求失败: ' + e.message;
- tip.className = 'tip error';
+ // 进程重启导致连接断开是正常的,或者登录失败
+ tip.textContent = '正在重启服务...';
+ tip.className = 'tip success';
+ setTimeout(() => location.reload(), 4000);
} finally {
btn.disabled = false;
btn.textContent = '登录并获取设备';
@@ -670,9 +684,18 @@
document.getElementById('btn-save-experimental').addEventListener('click', async function() {
const autoResume = document.getElementById('chk-auto-resume').checked;
const resumeDelay = parseInt(document.getElementById('input-resume-delay').value) || 5;
+ const dlnaPort = parseInt(document.getElementById('input-dlna-port').value) || 8200;
+ const webPort = parseInt(document.getElementById('input-web-port').value) || 8300;
const tip = document.getElementById('tip-experimental');
const btn = this;
+ // 端口验证
+ if (dlnaPort < 1 || dlnaPort > 65535 || webPort < 1 || webPort > 65535) {
+ tip.textContent = '端口必须在 1-65535 之间';
+ tip.className = 'tip error';
+ return;
+ }
+
btn.disabled = true;
btn.textContent = '保存中...';
tip.textContent = '';
@@ -684,6 +707,8 @@
body: JSON.stringify({
auto_resume_on_interrupt: autoResume,
resume_delay_seconds: resumeDelay,
+ dlna_port: dlnaPort,
+ web_port: webPort,
}),
});
const data = await resp.json();
@@ -693,6 +718,8 @@
tip.className = 'tip success';
currentSetting.auto_resume_on_interrupt = autoResume;
currentSetting.resume_delay_seconds = resumeDelay;
+ currentSetting.dlna_port = dlnaPort;
+ currentSetting.web_port = webPort;
} else {
tip.textContent = data.message || '保存失败';
tip.className = 'tip error';
diff --git a/preview.png b/preview.png
deleted file mode 100644
index 3e2b2949acc898ace019fc3ac9c325f05c3de6bd..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 179823
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