Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HLS quality support #134

Merged
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
11 changes: 11 additions & 0 deletions Core/Core/Data/CoreStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,14 @@ public protocol CoreStorage {
var userSettings: UserSettings? {get set}
func clear()
}

public struct CoreStorageMock: CoreStorage {
public var accessToken: String? = nil
public var refreshToken: String? = nil
public var cookiesDate: String? = nil
public var user: DataLayer.User? = nil
public var userSettings: UserSettings? = nil
public func clear() {}

public init() {}
}
8 changes: 4 additions & 4 deletions Core/Core/Data/Model/UserSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import Foundation

public struct UserSettings: Codable {
public var wifiOnly: Bool
public var downloadQuality: VideoQuality
public var streamingQuality: StreamingQuality

public init(wifiOnly: Bool, downloadQuality: VideoQuality) {
public init(wifiOnly: Bool, streamingQuality: StreamingQuality) {
self.wifiOnly = wifiOnly
self.downloadQuality = downloadQuality
self.streamingQuality = streamingQuality
}
}

public enum VideoQuality: Codable {
public enum StreamingQuality: Codable {
case auto
case low
case medium
Expand Down
4 changes: 3 additions & 1 deletion Course/Course/Presentation/Video/EncodedVideoPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public struct EncodedVideoPlayer: View {
PlayerViewController(
videoURL: viewModel.url,
controller: viewModel.controller,
bitrate: viewModel.getVideoResolution(),
progress: { progress in
if progress >= 0.8 {
if !isViewedOnce {
Expand Down Expand Up @@ -136,7 +137,8 @@ struct EncodedVideoPlayer_Previews: PreviewProvider {
languages: [],
playerStateSubject: CurrentValueSubject<VideoPlayerState?, Never>(nil),
interactor: CourseInteractor(repository: CourseRepositoryMock()),
router: CourseRouterMock(),
router: CourseRouterMock(),
appStorage: CoreStorageMock(),
connectivity: Connectivity()
),
isOnScreen: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class EncodedVideoPlayerViewModel: VideoPlayerViewModel {
playerStateSubject: CurrentValueSubject<VideoPlayerState?, Never>,
interactor: CourseInteractorProtocol,
router: CourseRouter,
appStorage: CoreStorage,
connectivity: ConnectivityProtocol
) {
self.url = url
Expand All @@ -32,7 +33,8 @@ public class EncodedVideoPlayerViewModel: VideoPlayerViewModel {
courseID: courseID,
languages: languages,
interactor: interactor,
router: router,
router: router,
appStorage: appStorage,
connectivity: connectivity)

playerStateSubject.sink(receiveValue: { [weak self] state in
Expand All @@ -46,4 +48,19 @@ public class EncodedVideoPlayerViewModel: VideoPlayerViewModel {
}
}).store(in: &subscription)
}

func getVideoResolution() -> CGSize {
switch appStorage.userSettings?.streamingQuality {
case .auto:
return CGSize(width: 1280, height: 720)
case .low:
return CGSize(width: 640, height: 360)
case .medium:
return CGSize(width: 854, height: 480)
case .high:
return CGSize(width: 1280, height: 720)
case .none:
return CGSize(width: 1280, height: 720)
}
}
}
7 changes: 6 additions & 1 deletion Course/Course/Presentation/Video/PlayerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ import _AVKit_SwiftUI
struct PlayerViewController: UIViewControllerRepresentable {

var videoURL: URL?
var videoResolution: CGSize
var controller: AVPlayerViewController
var progress: ((Float) -> Void)
var seconds: ((Double) -> Void)

init(
videoURL: URL?, controller: AVPlayerViewController,
videoURL: URL?,
controller: AVPlayerViewController,
bitrate: CGSize,
progress: @escaping ((Float) -> Void),
seconds: @escaping ((Double) -> Void)
) {
self.videoURL = videoURL
self.controller = controller
self.videoResolution = bitrate
self.progress = progress
self.seconds = seconds
}
Expand Down Expand Up @@ -75,6 +79,7 @@ struct PlayerViewController: UIViewControllerRepresentable {
playerController.player = AVPlayer()
}
playerController.player?.replaceCurrentItem(with: AVPlayerItem(url: videoURL!))
playerController.player?.currentItem?.preferredMaximumResolution = videoResolution
addPeriodicTimeObserver(playerController, currentProgress: { progress, seconds in
self.progress(progress)
self.seconds(seconds)
Expand Down
3 changes: 2 additions & 1 deletion Course/Course/Presentation/Video/SubtittlesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ struct SubtittlesView_Previews: PreviewProvider {
blockID: "", courseID: "",
languages: [],
interactor: CourseInteractor(repository: CourseRepositoryMock()),
router: CourseRouterMock(),
router: CourseRouterMock(),
appStorage: CoreStorageMock(),
connectivity: Connectivity()
), scrollTo: {_ in }
)
Expand Down
3 changes: 3 additions & 0 deletions Course/Course/Presentation/Video/VideoPlayerViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class VideoPlayerViewModel: ObservableObject {
private let interactor: CourseInteractorProtocol
public let connectivity: ConnectivityProtocol
public let router: CourseRouter
public let appStorage: CoreStorage

private var subtitlesDownloaded: Bool = false
@Published var subtitles: [Subtitle] = []
Expand All @@ -37,13 +38,15 @@ public class VideoPlayerViewModel: ObservableObject {
languages: [SubtitleUrl],
interactor: CourseInteractorProtocol,
router: CourseRouter,
appStorage: CoreStorage,
connectivity: ConnectivityProtocol
) {
self.blockID = blockID
self.courseID = courseID
self.languages = languages
self.interactor = interactor
self.router = router
self.appStorage = appStorage
self.connectivity = connectivity
self.prepareLanguages()
}
Expand Down
3 changes: 2 additions & 1 deletion Course/Course/Presentation/Video/YouTubeVideoPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ struct YouTubeVideoPlayer_Previews: PreviewProvider {
languages: [],
playerStateSubject: CurrentValueSubject<VideoPlayerState?, Never>(nil),
interactor: CourseInteractor(repository: CourseRepositoryMock()),
router: CourseRouterMock(),
router: CourseRouterMock(),
appStorage: CoreStorageMock(),
connectivity: Connectivity()),
isOnScreen: true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class YouTubeVideoPlayerViewModel: VideoPlayerViewModel {
playerStateSubject: CurrentValueSubject<VideoPlayerState?, Never>,
interactor: CourseInteractorProtocol,
router: CourseRouter,
appStorage: CoreStorage,
connectivity: ConnectivityProtocol
) {
self.url = url
Expand Down Expand Up @@ -61,6 +62,7 @@ public class YouTubeVideoPlayerViewModel: VideoPlayerViewModel {
languages: languages,
interactor: interactor,
router: router,
appStorage: appStorage,
connectivity: connectivity
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ final class VideoPlayerViewModelTests: XCTestCase {
courseID: "",
languages: [],
interactor: interactor,
router: router,
router: router,
appStorage: CoreStorageMock(),
connectivity: connectivity)

await viewModel.getSubtitles(subtitlesUrl: "url")
Expand All @@ -64,6 +65,7 @@ final class VideoPlayerViewModelTests: XCTestCase {
languages: [],
interactor: interactor,
router: router,
appStorage: CoreStorageMock(),
connectivity: connectivity)

await viewModel.getSubtitles(subtitlesUrl: "url")
Expand All @@ -85,6 +87,7 @@ final class VideoPlayerViewModelTests: XCTestCase {
languages: [],
interactor: interactor,
router: router,
appStorage: CoreStorageMock(),
connectivity: connectivity)

viewModel.languages = [
Expand Down Expand Up @@ -112,6 +115,7 @@ final class VideoPlayerViewModelTests: XCTestCase {
languages: [],
interactor: interactor,
router: router,
appStorage: CoreStorageMock(),
connectivity: connectivity)

Given(interactor, .blockCompletionRequest(courseID: .any, blockID: .any, willProduce: {_ in}))
Expand All @@ -131,6 +135,7 @@ final class VideoPlayerViewModelTests: XCTestCase {
languages: [],
interactor: interactor,
router: router,
appStorage: CoreStorageMock(),
connectivity: connectivity)

Given(interactor, .blockCompletionRequest(courseID: .any, blockID: .any, willThrow: NSError()))
Expand All @@ -155,6 +160,7 @@ final class VideoPlayerViewModelTests: XCTestCase {
languages: [],
interactor: interactor,
router: router,
appStorage: CoreStorageMock(),
connectivity: connectivity)

Given(interactor, .blockCompletionRequest(courseID: .any, blockID: .any, willThrow: noInternetError))
Expand Down
4 changes: 3 additions & 1 deletion OpenEdX/DI/ScreenAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ class ScreenAssembly: Assembly {
playerStateSubject: playerStateSubject,
interactor: r.resolve(CourseInteractorProtocol.self)!,
router: r.resolve(CourseRouter.self)!,
appStorage: r.resolve(CoreStorage.self)!,
connectivity: r.resolve(ConnectivityProtocol.self)!
)
}
Expand All @@ -295,7 +296,8 @@ class ScreenAssembly: Assembly {
languages: languages,
playerStateSubject: playerStateSubject,
interactor: r.resolve(CourseInteractorProtocol.self)!,
router: r.resolve(CourseRouter.self)!,
router: r.resolve(CourseRouter.self)!,
appStorage: r.resolve(CoreStorage.self)!,
connectivity: r.resolve(ConnectivityProtocol.self)!
)
}
Expand Down
2 changes: 1 addition & 1 deletion OpenEdX/Data/AppStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public class AppStorage: CoreStorage, ProfileStorage, WhatsNewStorage {
public var userSettings: UserSettings? {
get {
guard let userSettings = userDefaults.data(forKey: KEY_SETTINGS) else {
let defaultSettings = UserSettings(wifiOnly: true, downloadQuality: .auto)
let defaultSettings = UserSettings(wifiOnly: true, streamingQuality: .auto)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(defaultSettings) {
userDefaults.set(encoded, forKey: KEY_SETTINGS)
Expand Down
4 changes: 2 additions & 2 deletions Profile/Profile/Data/ProfileRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public class ProfileRepository: ProfileRepositoryProtocol {
if let userSettings = storage.userSettings {
return userSettings
} else {
return UserSettings(wifiOnly: true, downloadQuality: VideoQuality.auto)
return UserSettings(wifiOnly: true, streamingQuality: StreamingQuality.auto)
}
}

Expand Down Expand Up @@ -233,7 +233,7 @@ class ProfileRepositoryMock: ProfileRepositoryProtocol {
public func deleteAccount(password: String) async throws -> Bool { return false }

public func getSettings() -> UserSettings {
return UserSettings(wifiOnly: true, downloadQuality: .auto)
return UserSettings(wifiOnly: true, streamingQuality: .auto)
}
public func saveSettings(_ settings: UserSettings) {}
}
Expand Down
10 changes: 5 additions & 5 deletions Profile/Profile/Presentation/Settings/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ public class SettingsViewModel: ObservableObject {
}
}

@Published var selectedQuality: VideoQuality {
@Published var selectedQuality: StreamingQuality {
willSet {
if newValue != selectedQuality {
userSettings.downloadQuality = newValue
userSettings.streamingQuality = newValue
interactor.saveSettings(userSettings)
}
}
}
let quality = Array([VideoQuality.auto, VideoQuality.low, VideoQuality.medium, VideoQuality.high].enumerated())
let quality = Array([StreamingQuality.auto, StreamingQuality.low, StreamingQuality.medium, StreamingQuality.high].enumerated())

var errorMessage: String? {
didSet {
Expand All @@ -51,11 +51,11 @@ public class SettingsViewModel: ObservableObject {

self.userSettings = interactor.getSettings()
self.wifiOnly = userSettings.wifiOnly
self.selectedQuality = userSettings.downloadQuality
self.selectedQuality = userSettings.streamingQuality
}
}

extension VideoQuality {
extension StreamingQuality {

func title() -> String {
switch self {
Expand Down
8 changes: 4 additions & 4 deletions Profile/Profile/SwiftGen/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ public enum ProfileLocalization {
public static let title = ProfileLocalization.tr("Localizable", "LOGOUT_ALERT.TITLE", fallback: "Comfirm log out")
}
public enum Settings {
/// Smallest video quality
public static let quality360Description = ProfileLocalization.tr("Localizable", "SETTINGS.QUALITY_360_DESCRIPTION", fallback: "Smallest video quality")
/// Lower data usage
public static let quality360Description = ProfileLocalization.tr("Localizable", "SETTINGS.QUALITY_360_DESCRIPTION", fallback: "Lower data usage")
/// 360p
public static let quality360Title = ProfileLocalization.tr("Localizable", "SETTINGS.QUALITY_360_TITLE", fallback: "360p")
/// 540p
Expand All @@ -128,8 +128,8 @@ public enum ProfileLocalization {
public static let version = ProfileLocalization.tr("Localizable", "SETTINGS.VERSION", fallback: "Version:")
/// Auto (Recommended)
public static let videoQualityDescription = ProfileLocalization.tr("Localizable", "SETTINGS.VIDEO_QUALITY_DESCRIPTION", fallback: "Auto (Recommended)")
/// Video download quality
public static let videoQualityTitle = ProfileLocalization.tr("Localizable", "SETTINGS.VIDEO_QUALITY_TITLE", fallback: "Video download quality")
/// Video streaming quality
public static let videoQualityTitle = ProfileLocalization.tr("Localizable", "SETTINGS.VIDEO_QUALITY_TITLE", fallback: "Video streaming quality")
/// Video settings
public static let videoSettingsTitle = ProfileLocalization.tr("Localizable", "SETTINGS.VIDEO_SETTINGS_TITLE", fallback: "Video settings")
/// Only download content when wi-fi is turned on
Expand Down
4 changes: 2 additions & 2 deletions Profile/Profile/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@
"SETTINGS.VIDEO_SETTINGS_TITLE" = "Video settings";
"SETTINGS.WIFI_TITLE" = "Wi-fi only download";
"SETTINGS.WIFI_DESCRIPTION" = "Only download content when wi-fi is turned on";
"SETTINGS.VIDEO_QUALITY_TITLE" = "Video download quality";
"SETTINGS.VIDEO_QUALITY_TITLE" = "Video streaming quality";
"SETTINGS.VIDEO_QUALITY_DESCRIPTION" = "Auto (Recommended)";

"SETTINGS.QUALITY_AUTO_TITLE" = "Auto";
"SETTINGS.QUALITY_AUTO_DESCRIPTION" = "Recommended";
"SETTINGS.QUALITY_360_TITLE" = "360p";
"SETTINGS.QUALITY_360_DESCRIPTION" = "Smallest video quality";
"SETTINGS.QUALITY_360_DESCRIPTION" = "Lower data usage";
"SETTINGS.QUALITY_540_TITLE" = "540p";
"SETTINGS.QUALITY_720_TITLE" = "720p";
"SETTINGS.QUALITY_720_DESCRIPTION" = "Best quality";
Expand Down
4 changes: 2 additions & 2 deletions Profile/Profile/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@
"SETTINGS.VIDEO_SETTINGS_TITLE" = "Налаштування відео";
"SETTINGS.WIFI_TITLE" = "Тільки Wi-fi";
"SETTINGS.WIFI_DESCRIPTION" = "Завантажувати відео, лише коли Wi-Fi увімкнено";
"SETTINGS.VIDEO_QUALITY_TITLE" = "Якість відео";
"SETTINGS.VIDEO_QUALITY_TITLE" = "Якість потокового відео";
"SETTINGS.VIDEO_QUALITY_DESCRIPTION" = "Авто (Рекомендовано)";

"SETTINGS.QUALITY_AUTO_TITLE" = "Авто";
"SETTINGS.QUALITY_AUTO_DESCRIPTION" = "Рекомендовано";
"SETTINGS.QUALITY_360_TITLE" = "360p";
"SETTINGS.QUALITY_360_DESCRIPTION" = "Найменша якість відео";
"SETTINGS.QUALITY_360_DESCRIPTION" = "економія трафіку";
"SETTINGS.QUALITY_540_TITLE" = "540p";
"SETTINGS.QUALITY_720_TITLE" = "720p";
"SETTINGS.QUALITY_AUTO_DESCRIPTION" = "Найкраща якість";
Expand Down