Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@
ReferencedContainer = "container:AudioPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "disable"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ struct AudioPlayerControls: View {
}
.buttonStyle(.plain)
.padding(.leading, 8)
Button(action: {
model.cycleLoopMode()
}) {
Image(systemName: model.iconForLoopMode)
.symbolVariant(model.isLoopActive ? .circle.fill : .none)
.font(.title)
.imageScale(.small)
.foregroundStyle(model.isLoopActive ? .mint : .gray)
}
.buttonStyle(.plain)
.padding(.leading, 8)
.help(model.loopModeDescription)
Spacer()
HStack {
Slider(value: $model.volume)
Expand Down Expand Up @@ -101,6 +113,31 @@ struct AudioPlayerControls: View {
}
.padding(.bottom, 8)
.padding(.horizontal, 16)

if model.isLoopActive {
Divider()
VStack(alignment: .leading) {
Text("Loop Times: \(model.loopTimes == 0 ? "∞" : "\(Int(model.loopTimes))")")
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(.black)
HStack {
Text("1")
.font(.caption)
Slider(value: $model.loopTimes, in: 0...10, step: 1)
.onChange(of: model.loopTimes) { _, new in
model.updateLoopTimes(new)
}
Text("∞")
.font(.caption)
}
Text(model.loopTimes == 0 ? "Loop infinitely" : "Loop \(Int(model.loopTimes)) time\(Int(model.loopTimes) == 1 ? "" : "s")")
.font(.caption2)
.foregroundStyle(.secondary)
}
.padding(.bottom, 8)
.padding(.horizontal, 16)
}
}
.onChange(of: currentTrack) { oldValue, newValue in
if let track = newValue {
Expand Down Expand Up @@ -132,6 +169,9 @@ extension AudioPlayerControls {

var isPlaying: Bool = false
var isMuted: Bool = false

var loopMode: AudioPlayerLoopMode = .off
var loopTimes: Double = 0 // 0 means infinite

var volume: Float = 0.5

Expand Down Expand Up @@ -159,6 +199,52 @@ extension AudioPlayerControls {
return "speaker.wave.3"
}
}

var iconForLoopMode: String {
switch loopMode {
case .off:
return "repeat"
case .single:
return "repeat.1"
case .all:
return "repeat"
}
}

var loopModeDescription: String {
switch loopMode {
case .off:
return "Loop: Off"
case .single(let times):
if let times = times, times > 0 {
return "Loop: Single (\(times)x)"
}
return "Loop: Single (∞)"
case .all(let times):
if let times = times, times > 0 {
return "Loop: All (\(times)x)"
}
return "Loop: All (∞)"
}
}

var isLoopActive: Bool {
if case .off = loopMode {
return false
}
return true
}

var currentLoopTimes: Int? {
switch loopMode {
case .off:
return nil
case .single(let times):
return times
case .all(let times):
return times
}
}

init(audioPlayerService: AudioPlayerService) {
self.audioPlayerService = audioPlayerService
Expand Down Expand Up @@ -220,6 +306,19 @@ extension AudioPlayerControls {
isMuted.toggle()
audioPlayerService.toggleMute()
}

func cycleLoopMode() {
audioPlayerService.cycleLoopMode()
loopMode = audioPlayerService.loopMode
// Update loopTimes to match the current mode's times
loopTimes = Double(currentLoopTimes ?? 0)
}

func updateLoopTimes(_ times: Double) {
let timesValue = times == 0 ? nil : Int(times)
audioPlayerService.setLoopTimes(timesValue)
loopMode = audioPlayerService.loopMode
}

func playPause() {
if audioPlayerService.state == .playing {
Expand Down
31 changes: 31 additions & 0 deletions AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ final class AudioPlayerService {
var state: AudioPlayerState {
player.state
}

var loopMode: AudioPlayerLoopMode {
get { player.loopMode }
set { player.loopMode = newValue }
}

var statusChangedNotifier = Notifier<AudioPlayerState>()
var metadataReceivedNotifier = Notifier<[String: String]>()
Expand Down Expand Up @@ -114,6 +119,32 @@ final class AudioPlayerService {
func seek(at time: Double) {
player.seek(to: time)
}

func setLoopMode(_ mode: AudioPlayerLoopMode) {
player.loopMode = mode
}

func cycleLoopMode() {
switch player.loopMode {
case .off:
player.loopMode = .single(times: nil)
case .single:
player.loopMode = .all(times: nil)
case .all:
player.loopMode = .off
}
}

func setLoopTimes(_ times: Int?) {
switch player.loopMode {
case .off:
break
case .single:
player.loopMode = .single(times: times)
case .all:
player.loopMode = .all(times: times)
}
}

private func recreatePlayer() {
player = audioPlayerProvider()
Expand Down
31 changes: 19 additions & 12 deletions AudioStreaming/Core/Helpers/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
//

import Foundation
import os
import OSLog

private let loggingSubsystem = "audio.streaming.log"

enum Logger {
private static let audioRendering = OSLog(subsystem: loggingSubsystem, category: "audio.rendering")
private static let networking = OSLog(subsystem: loggingSubsystem, category: "audio.networking")
private static let generic = OSLog(subsystem: loggingSubsystem, category: "audio.streaming.generic")
extension Logger {
public static let audioRendering = Logger(subsystem: loggingSubsystem, category: "audio.rendering")
public static let networking = Logger(subsystem: loggingSubsystem, category: "audio.networking")
public static let generic = Logger(subsystem: loggingSubsystem, category: "audio.streaming.generic")

/// Defines is the the logger displays any logs
static var isEnabled = true
Expand All @@ -21,7 +21,7 @@ enum Logger {
case networking
case generic

func toOSLog() -> OSLog {
func toOSLog() -> Logger {
switch self {
case .audioRendering: return Logger.audioRendering
case .networking: return Logger.networking
Expand All @@ -30,24 +30,31 @@ enum Logger {
}
}

static func error(_ message: StaticString, category: Category, args: CVarArg...) {
static func error(_ message: String, category: Category, args: CVarArg...) {
process(message, category: category, type: .error, args: args)
}

static func error(_ message: StaticString, category: Category) {
static func error(_ message: String, category: Category) {
error(message, category: category, args: [])
}

static func debug(_ message: StaticString, category: Category, args: CVarArg...) {
static func debug(_ message: String, category: Category, args: CVarArg...) {
process(message, category: category, type: .debug, args: args)
}

static func debug(_ message: StaticString, category: Category) {
static func debug(_ message: String, category: Category) {
debug(message, category: category, args: [])
}

private static func process(_ message: StaticString, category: Category, type: OSLogType, args: CVarArg...) {
private static func process(_ message: String, category: Category, type: OSLogType, args: CVarArg...) {
guard isEnabled else { return }
os_log(message, log: category.toOSLog(), type: type, args)
switch type {
case .debug:
category.toOSLog().debug("\(message)")
case .error:
category.toOSLog().error("\(message)")
default:
category.toOSLog().info("\(message)")
}
}
}
1 change: 1 addition & 0 deletions AudioStreaming/OggVorbis/VorbisFileDecoder.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import AudioCodecs
import AVFoundation
import OSLog

/// A simple decoder for Ogg Vorbis files using libvorbisfile
final class VorbisFileDecoder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

import Foundation
import OSLog

struct MP4Atom: Equatable, CustomDebugStringConvertible {
let type: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

import Foundation
import OSLog

final class RemoteMp4Restructure {
struct RestructuredData {
Expand Down
Loading