Close revoked remote sessions and document installation requirements
iOS build and tests / simulator (push) Has been cancelled

This commit is contained in:
2026-09-14 00:51:20 +03:00
parent eaf091b1cf
commit 91e305ada5
5 changed files with 65 additions and 7 deletions
+4 -2
View File
@@ -32,8 +32,10 @@ struct ContentView: View {
Label(systemPlayer.running ? "Выключить системный пульт" : "Включить системный пульт iOS", systemImage: "play.rectangle") Label(systemPlayer.running ? "Выключить системный пульт" : "Включить системный пульт iOS", systemImage: "play.rectangle")
}.disabled(systemPlayer.busy || (!systemPlayer.running && (!store.connected || store.state?.hasSession != true))) }.disabled(systemPlayer.busy || (!systemPlayer.running && (!store.connected || store.state?.hasSession != true)))
if systemPlayer.busy { ProgressView() } if systemPlayer.busy { ProgressView() }
Text("Музыка ПК в Пункте управления. Звук остаётся на компьютере.") Text("Музыка ПК в Пункте управления. Звук остаётся на компьютере.")
.font(.caption).foregroundStyle(.secondary) .font(.caption).foregroundStyle(.secondary)
Text("Без фоновых push-обновлений трек обновляется при открытом приложении или после команды пульта.")
.font(.caption).foregroundStyle(.secondary)
if let error = systemPlayer.error { Text(error).font(.caption).foregroundStyle(.red).textSelection(.enabled) } if let error = systemPlayer.error { Text(error).font(.caption).foregroundStyle(.red).textSelection(.enabled) }
}.frame(maxWidth: .infinity, alignment: .leading).bridgeCard() }.frame(maxWidth: .infinity, alignment: .leading).bridgeCard()
} }
+1
View File
@@ -27,6 +27,7 @@ final class SystemPlayerManager: ObservableObject {
do { do {
try SystemPlayerCredentials.save(connection, id: newID) try SystemPlayerCredentials.save(connection, id: newID)
_ = try SystemPlayerCredentials.load(newID) _ = try SystemPlayerCredentials.load(newID)
UserDefaults.standard.set(newID, forKey: savedIDKey)
let session = try await RemoteMediaSession.start(attributes: SystemPlayerAttributes(id: newID, state: state)) let session = try await RemoteMediaSession.start(attributes: SystemPlayerAttributes(id: newID, state: state))
guard generation == operation else { try? await session.end(); return } guard generation == operation else { try? await session.end(); return }
storage = session storage = session
+12 -3
View File
@@ -45,7 +45,8 @@ final class PCMediaSession: RemoteMediaSessionRepresentable {
} }
var commands: [MediaCommand] { var commands: [MediaCommand] {
[ guard attributes.hasSession else { return [] }
return [
.play { try await self.send(.play) }.enabled(attributes.capabilities.play), .play { try await self.send(.play) }.enabled(attributes.capabilities.play),
.pause { try await self.send(.pause) }.enabled(attributes.capabilities.pause), .pause { try await self.send(.pause) }.enabled(attributes.capabilities.pause),
.next { try await self.send(.next) }.enabled(attributes.capabilities.next), .next { try await self.send(.next) }.enabled(attributes.capabilities.next),
@@ -62,7 +63,15 @@ final class PCMediaSession: RemoteMediaSessionRepresentable {
} }
private func send(_ action: SystemPlayerAction, value: Double? = nil) async throws { private func send(_ action: SystemPlayerAction, value: Double? = nil) async throws {
let state = try await transport.send(action, value: value, sessionID: id) do {
attributes = SystemPlayerAttributes(id: id, state: state) let state = try await transport.send(action, value: value, sessionID: id)
attributes = SystemPlayerAttributes(id: id, state: state)
} catch BridgeError.unauthorized {
try SystemPlayerCredentials.delete(id)
attributes.hasSession = false
attributes.playing = false
attributes.volume = nil
throw BridgeError.unauthorized
}
} }
} }
+31 -2
View File
@@ -1,9 +1,38 @@
# MusicBridge iOS — план # MusicBridge iOS — план
Обновлено: 2026-09-13. Пользователь разрешил начать нативное iOS-приложение. Обновлено: 2026-09-14. Пользователь разрешил начать нативное iOS-приложение.
Репозиторий: `C:/Users/areso/iOS MusicBridge`; origin — dev.yukinoki.ru/musicbridge/ios. Репозиторий: `C:/Users/areso/iOS MusicBridge`; origin — dev.yukinoki.ru/musicbridge/ios.
Windows-агент — соседний проект `C:/Users/areso/MusicBridge/MusicBridge.Agent`. Windows-агент — соседний проект `C:/Users/areso/MusicBridge/MusicBridge.Agent`.
## Системный пульт 0.7.0 (8) — текущий этап
- [x] Реализация NowPlaying RemoteMediaSession и отдельного ExtensionKit target для iOS 27.
- [x] Кнопка включения/выключения в приложении; музыка продолжает играть на ПК.
- [x] Метаданные, обложка, play/pause, предыдущий/следующий трек, перемотка, громкость ПК.
- [x] Перед командой проверяются свежие capabilities и действующее сопряжение; команды не повторяются автоматически.
- [x] Токен хранится в общем Keychain, не передаётся в атрибутах системной сессии.
- [x] Перемотка переводится из позиции относительно трека в диапазон Windows API.
- [x] Выключение и удаление сопряжения удаляют доступ расширения; возврат запоздавшего restore не восстанавливает сессию.
- [ ] Успешная итоговая сборка и XCTest на Xcode 27; первая попытка 34783380221 выявила Double/Float и исправлена.
- [ ] Установка с подписью обоих расширений и общим Keychain; проверка на физическом iPhone.
- [ ] Пункт управления: кнопки, перемотка, громкость; блокировка телефона, сон ПК, отзыв токена, повторная подпись.
**Ограничение iloader:** проверен официальный исходник `nab138/isideload`, ветка
`apple-codesign-quick`, commit `f6a4d5dba717d72fc2af63eaba26b27ba44116be`, который использует текущий iloader.
`isideload/src/sideload/bundle.rs` обнаруживает расширения только в `PlugIns`,
а NowPlaying размещается в `Extensions`. `sign.rs` берёт entitlements из profile,
добавление shared Keychain реализовано для отдельных SpecialApp, не для MusicBridge.
Поэтому обычную установку 0.7.0 через iloader нельзя считать поддержанной.
Не переносить ExtensionKit в PlugIns наугад и не передавать токен через публичные атрибуты.
Следующий этап установки: совместимый подписыватель или доработка iloader для
ExtensionKit и общего Keychain с реальными provisioning profiles. Xcode signing
всех трёх targets — вариант при наличии совместимого Mac и профилей; разрешение
нового extension point бесплатной командой ещё не подтверждено на устройстве.
Без APNs расширение читает ПК при команде; открытое приложение обновляет сессию
при смене трека/статуса и периодически. Смена трека на самом ПК при приостановленном
приложении не гарантирует автоматического обновления карточки. Фиктивное аудио не используется.
## Инфраструктура — текущее состояние ## Инфраструктура — текущее состояние
- SSH к macOS проверен: `aresov@10.67.67.28`, macOS 26.6.2, x86_64, - SSH к macOS проверен: `aresov@10.67.67.28`, macOS 26.6.2, x86_64,
@@ -73,7 +102,7 @@ Windows-агент — соседний проект `C:/Users/areso/MusicBridge
- [x] LiveActivityIntent: предыдущий/следующий трек, play/pause и обновление — 0.6.0. - [x] LiveActivityIntent: предыдущий/следующий трек, play/pause и обновление — 0.6.0.
- [ ] Проверить команды из Live Activity/Island при блокировке на физическом iPhone. - [ ] Проверить команды из Live Activity/Island при блокировке на физическом iPhone.
- [ ] Команды Siri. - [ ] Команды Siri.
- [ ] NowPlaying RemoteMediaSession (iOS 27+): перейти на SDK 27, добавить remote-media extension и проверить системный Control Center. - [x] NowPlaying RemoteMediaSession (iOS 27+): SDK 27 и remote-media extension реализованы в 0.7.0; установка и физическая проверка — в текущем этапе выше.
- [ ] APNs: отдельный проект обновлений при приостановленном приложении, если нужен постоянный актуальный трек в фоне. - [ ] APNs: отдельный проект обновлений при приостановленном приложении, если нужен постоянный актуальный трек в фоне.
Ограничения: хранится один доверенный ПК; адрес при смене IP восстанавливается через Ограничения: хранится один доверенный ПК; адрес при смене IP восстанавливается через
+17
View File
@@ -23,6 +23,23 @@ Windows-агентом и подключение по QR. В 0.4.0 добавл
## Сборка на Mac ## Сборка на Mac
Версия **0.7.0 (8)** добавляет системный пульт iOS 27: в плеере нажать
«Включить системный пульт iOS», затем открыть Пункт управления. Реализованы
play/pause, следующий/предыдущий трек, перемотка и громкость ПК. Звук остаётся на ПК.
Проверка этой функции на физическом iPhone пока не завершена.
Для этой версии нужно подписать **MusicBridge, MusicBridgeWidgets и MusicBridgeRemote**.
У приложения и MusicBridgeRemote должны совпадать разрешённые Keychain Sharing
groups: `<AppIdentifierPrefix>ru.yukinoki.musicbridge.remote-control`.
Префикс определяется из подписи при запуске, без хранения Apple Account в проекте.
MusicBridgeRemote — ExtensionKit в `Extensions`, а WidgetKit — в `PlugIns`.
Текущий iloader не обрабатывает эту комбинацию полностью: обычную переподпись
0.7.0 считать поддержанной нельзя. Для прежней установки оставлена IPA 0.6.0 ниже.
Состояние работ и требования к подписывателю описаны в `PLAN.md`.
Без APNs трек обновляется из открытого приложения или после команды системного
пульта. Изменения на ПК при приостановленном приложении могут отображаться с задержкой.
1. Открыть `MusicBridge.xcodeproj` в Xcode 27. 1. Открыть `MusicBridge.xcodeproj` в Xcode 27.
2. Выбрать схему MusicBridge и симулятор iPhone. Run запускает приложение, 2. Выбрать схему MusicBridge и симулятор iPhone. Run запускает приложение,
Test выполняет `MusicBridgeTests`. Test выполняет `MusicBridgeTests`.