Close revoked remote sessions and document installation requirements
iOS build and tests / simulator (push) Has been cancelled
iOS build and tests / simulator (push) Has been cancelled
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 восстанавливается через
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
Reference in New Issue
Block a user