Skip to content

Commit

Permalink
add PlayerCore-related files
Browse files Browse the repository at this point in the history
  • Loading branch information
Lee Jun Kit committed Oct 9, 2021
1 parent 2baa905 commit 35243b6
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 16 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@ fastlane/test_output
iOSInjectionProject/

# Rust
PlayerCore/target/**
PlayerCore/target/**
Spottie/PlayerCore/libspottie_player_core.*

131 changes: 117 additions & 14 deletions Spottie/PlayerCore/PlayerCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,129 @@
//

import Foundation
import Combine

class PlayerCore {
func run() {
class CaptureBag {
let jsonDecoder = JSONDecoder()
let currentState$: CurrentValueSubject<PlayerCoreState?, Never>
init(stateSubject: CurrentValueSubject<PlayerCoreState?, Never>) {
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
jsonDecoder.dateDecodingStrategy = .secondsSince1970
currentState$ = stateSubject
}
}
let captureBag: CaptureBag
let curlClient: CurlClient

private var state$: CurrentValueSubject<PlayerCoreState?, Never> = CurrentValueSubject(nil)
var statePublisher: AnyPublisher<PlayerCoreState?, Never> {
state$.eraseToAnyPublisher()
}

private var cancellables = [AnyCancellable]()

init() {
let socketPath = PlayerCore.getSocketPath()
curlClient = CurlClient(socketPath: socketPath)
captureBag = CaptureBag(stateSubject: state$)
run(socketPath: socketPath)
}

static func getSocketPath() -> String {
let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "YW4H592L4M.ljk.spottie")!
return container.appendingPathComponent("player_core.sock").path
}

func run(socketPath: String) {
// https://oleb.net/blog/2015/06/c-callbacks-in-swift/
let b = Unmanaged.passUnretained(captureBag);
let t = Thread.init {
/*
librespot_init(f.toOpaque()) { user_data, ptr, len in
if let pointer = ptr {
let data = Data(bytes: pointer, count: Int(len))
do {
let ff: Foo = Unmanaged.fromOpaque(user_data!).takeUnretainedValue();
let json = try ff.rawDecode(data)
print(json)
} catch {
print("json error: \(error.localizedDescription)")
}
librespot_init(socketPath, b.toOpaque()) { user_data, ptr, len in
guard let pointer = ptr else { return }
let data = Data(bytes: pointer, count: Int(len))
let bag: CaptureBag = Unmanaged.fromOpaque(user_data!).takeUnretainedValue();
if let stateUpdate = try? bag.jsonDecoder.decode(PlayerCoreState.self, from: data) {
bag.currentState$.send(stateUpdate)
}
if let debugObj = try? JSONSerialization.jsonObject(with: data, options: []) {
print(debugObj)
}
}
*/
}
t.name = "AnvilRust"
t.name = "ljk.spottie.PlayerCore"
t.start()
}

private func waitForPlayerCoreToBeReady() async {
return await withCheckedContinuation { continuation in
statePublisher.compactMap { $0 }.first().sink { _ in
continuation.resume()
}.store(in: &cancellables)
}
}

func token() async -> Result<TokenObject, CurlError> {
await waitForPlayerCoreToBeReady()

var req = URLRequest(url: URL(string: "http://localhost/token")!)
req.httpMethod = "POST"
let result = await curlClient.run(req)

switch result {
case .success(let received):
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let tokenObj = try? decoder.decode(TokenObject.self, from: received.data!)
return Result.success(tokenObj!)
case .failure(let err):
return Result.failure(err)
}
}

func play(_ trackIds: [String]) async {
let jsonBody = try! JSONSerialization.data(withJSONObject: ["track_ids": trackIds], options: [])

var req = URLRequest(url: URL(string: "http://localhost/queue/play")!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = jsonBody

let result = await curlClient.run(req)
switch result {
case .failure(let err):
print("Failure: \(err)")
case .success(let response):
print("Success! \(response)")
}
}

func togglePlayback() async {
var req = URLRequest(url: URL(string: "http://localhost/queue/toggle")!)
req.httpMethod = "POST"
let _ = await curlClient.run(req)
}

func next() async {
var req = URLRequest(url: URL(string: "http://localhost/queue/next")!)
req.httpMethod = "POST"
let _ = await curlClient.run(req)
}

func previous() async {
var req = URLRequest(url: URL(string: "http://localhost/queue/previous")!)
req.httpMethod = "POST"
let _ = await curlClient.run(req)
}

func seek(_ positionMs: Int) async {
let jsonBody = try! JSONSerialization.data(withJSONObject: ["position_ms": positionMs], options: [])

var req = URLRequest(url: URL(string: "http://localhost/player/seek")!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = jsonBody

let _ = await curlClient.run(req)
}
}
28 changes: 28 additions & 0 deletions Spottie/PlayerCore/PlayerCoreState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// PlayerCoreState.swift
// PlayerCoreState
//
// Created by Lee Jun Kit on 24/8/21.
//

import Foundation

struct PlayerCoreState: Decodable {
struct Player: Decodable {
enum PlayState: String, Decodable {
case playing, paused, stopped
}

let state: PlayState
let since: Date?
let elapsed: Double?
let trackId: String?
}

struct Queue: Decodable {
let currentIndex: Int?
}

let player: Player
let queue: Queue
}
2 changes: 1 addition & 1 deletion Spottie/Spottie-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//

#include "player_core.h"
#include "libspottie_player_core.h"
#include "spottie_curl.h"

0 comments on commit 35243b6

Please sign in to comment.