first commit
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 290 KiB |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon 1.jpg",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
import Foundation
|
||||
import WatchConnectivity
|
||||
import Combine
|
||||
|
||||
// MARK: - Лёгкая модель трека для передачи на часы
|
||||
struct WatchTrackInfo: Codable {
|
||||
let id: String // используем имя файла как стабильный идентификатор
|
||||
let title: String
|
||||
let artist: String
|
||||
}
|
||||
|
||||
// MARK: - Связь iPhone <-> Apple Watch
|
||||
// Добавь этот файл в таргет "music player" (основное приложение).
|
||||
class PhoneConnectivityManager: NSObject, WCSessionDelegate, ObservableObject {
|
||||
static let shared = PhoneConnectivityManager()
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
activateSession()
|
||||
// Откладываем обращение к MusicManager.shared на следующий "тик" —
|
||||
// иначе при первом запуске получится циклическая инициализация
|
||||
// двух синглтонов друг через друга и приложение упадёт.
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.observeMusicManager()
|
||||
}
|
||||
}
|
||||
|
||||
private func activateSession() {
|
||||
guard WCSession.isSupported() else { return }
|
||||
let session = WCSession.default
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
}
|
||||
|
||||
// MARK: - Отправка состояния плеера на часы
|
||||
|
||||
private func observeMusicManager() {
|
||||
let manager = MusicManager.shared
|
||||
Publishers.CombineLatest4(
|
||||
manager.$tracks,
|
||||
manager.$currentTrackIndex,
|
||||
manager.$isPlaying,
|
||||
manager.$currentTitle
|
||||
)
|
||||
.debounce(for: .milliseconds(250), scheduler: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.pushStateToWatch()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func pushStateToWatch() {
|
||||
guard WCSession.default.activationState == .activated else { return }
|
||||
let manager = MusicManager.shared
|
||||
|
||||
let trackInfos = manager.tracks.map {
|
||||
WatchTrackInfo(id: $0.url.lastPathComponent, title: $0.title, artist: $0.artist)
|
||||
}
|
||||
guard let tracksData = try? JSONEncoder().encode(trackInfos) else { return }
|
||||
|
||||
let context: [String: Any] = [
|
||||
"tracks": tracksData,
|
||||
"isPlaying": manager.isPlaying,
|
||||
"currentTitle": manager.currentTitle,
|
||||
"currentArtist": manager.currentArtist,
|
||||
"currentId": manager.currentTrack?.url.lastPathComponent ?? ""
|
||||
]
|
||||
|
||||
try? WCSession.default.updateApplicationContext(context)
|
||||
}
|
||||
|
||||
// MARK: - WCSessionDelegate
|
||||
|
||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||
DispatchQueue.main.async { self.pushStateToWatch() }
|
||||
}
|
||||
|
||||
func sessionDidBecomeInactive(_ session: WCSession) {}
|
||||
func sessionDidDeactivate(_ session: WCSession) { WCSession.default.activate() }
|
||||
|
||||
// Команды, пришедшие пока приложение на телефоне активно
|
||||
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
|
||||
handleCommand(message)
|
||||
}
|
||||
|
||||
// Команды, пришедшие "в очереди" (если телефон был недоступен)
|
||||
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any]) {
|
||||
handleCommand(userInfo)
|
||||
}
|
||||
|
||||
// MARK: - Обработка команд с часов
|
||||
|
||||
private func handleCommand(_ command: [String: Any]) {
|
||||
guard let action = command["action"] as? String else { return }
|
||||
DispatchQueue.main.async {
|
||||
let manager = MusicManager.shared
|
||||
switch action {
|
||||
case "playPause":
|
||||
manager.playPause()
|
||||
case "next":
|
||||
manager.nextTrack()
|
||||
case "previous":
|
||||
manager.prevTrack()
|
||||
case "playTrack":
|
||||
if let id = command["id"] as? String,
|
||||
let index = manager.currentList.firstIndex(where: { $0.url.lastPathComponent == id }) {
|
||||
manager.playTrack(at: index)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.pushStateToWatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// music_playerApp.swift
|
||||
// music player
|
||||
//
|
||||
// Created by aswer on 23.06.2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct music_playerApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user