Skip to content

Commit

Permalink
Switch to YToke dedicated backend and updates #19 (#20)
Browse files Browse the repository at this point in the history
Endpoint switch
Add tracking for impression and video tag
Display tag and finishing percentage on video list
  • Loading branch information
YuantongL authored Sep 16, 2020
1 parent 88bb94e commit dd192b0
Show file tree
Hide file tree
Showing 53 changed files with 1,291 additions and 380 deletions.
Binary file modified .DS_Store
Binary file not shown.
142 changes: 115 additions & 27 deletions YToke.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Binary file not shown.
89 changes: 0 additions & 89 deletions YToke/Data/VideoListProvider/InvidiousAPIVideoListProvider.swift

This file was deleted.

113 changes: 113 additions & 0 deletions YToke/Data/VideoListProvider/YTokeBackendVideoListProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// YTokeBackendVideoListProvider.swift
// YToke
//
// Created by Lyt on 9/14/20.
// Copyright © 2020 TestOrganization. All rights reserved.
//

import Foundation
import os.log

enum YTokeBackendError: Error {
case unableToGenerateURL
case fetchDataError
case parseDataError
}

final class YTokeBackendVideoListProvider: VideoListProvider {

private static let endpoint = "https://ytokebackend.appspot.com/videos"

private let session: URLSession

init(session: URLSession = URLSession.shared) {
self.session = session
}

func fetch(query: String, page: Int, onCompletion: @escaping (VideoListProviderResult) -> Void) {
var urlComponents = URLComponents(string: Self.endpoint)
urlComponents?.queryItems = [
URLQueryItem(name: "q", value: "\(query)"),
URLQueryItem(name: "page", value: "\(page)")
]

guard let url = urlComponents?.url else {
onCompletion(.failure(YTokeBackendError.unableToGenerateURL))
return
}

let urlRequest = URLRequest(url: url)
let task = session.dataTask(with: urlRequest) { (data, _, error) in
if error != nil {
onCompletion(.failure(YTokeBackendError.fetchDataError))
return
}

guard let data = data else {
onCompletion(.failure(YTokeBackendError.fetchDataError))
return
}

do {
let result = try JSONDecoder().decode(YTokeBackendResponse.self, from: data)
onCompletion(.success(result.videos.map { $0.video }))
} catch {
os_log("ERROR PARSING JSON DATA")
onCompletion(.failure(YTokeBackendError.parseDataError))
return
}
}
task.resume()
}
}

private struct YTokeBackendResponse: Decodable {

let q: String
let videos: [Video]

struct Video: Decodable {
let title: String
let videoId: String
let thumbnails: [Thumbnail]
let tags: [String]?
let percentageFinished: Float?
}

struct Thumbnail: Decodable {
let quality: String
let url: String
}
}

private extension YTokeBackendResponse.Video {
var video: Video {
if let urlString = thumbnails.first(where: { $0.quality == "high" })?.url {
return Video(id: self.videoId,
title: self.title,
thumbnail: URL(string: urlString),
percentageFinished: self.percentageFinished,
tag: self.tags?.compactMap { $0.videoTag })
} else {
return Video(id: self.videoId,
title: self.title,
thumbnail: nil,
percentageFinished: self.percentageFinished,
tag: self.tags?.compactMap { $0.videoTag })
}
}
}

private extension String {
var videoTag: VideoTag? {
switch self {
case "OFF_VOCAL":
return .offVocal
case "WITH_VOCAL":
return .withVocal
default:
return nil
}
}
}
66 changes: 66 additions & 0 deletions YToke/Data/VideoStats/StandardVideoStatsMutationProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// StandardVideoStatsMutationProvider.swift
// YToke
//
// Created by Lyt on 9/14/20.
// Copyright © 2020 TestOrganization. All rights reserved.
//

import Foundation

final class StandardVideoStatsMutationProvider: VideoStatsMutationProvider {

private static let endpoint = "https://ytokebackend.appspot.com/video/stats"

private let session: URLSession

init(session: URLSession = URLSession.shared) {
self.session = session
}

func reportTag(videoId: String, tag: VideoTag) {
let parameters = "videoId=\(videoId)&tag=\(tag.queryParameter)"
let postData = parameters.data(using: .utf8)

guard let url = URL(string: "\(Self.endpoint)/tag") else {
return
}

var request = URLRequest(url: url)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData

let task = session.dataTask(with: request)
task.resume()
}

func reportImpression(videoId: String, percentage: Double) {
let parameters = "videoId=\(videoId)&percentage=\(percentage)"
let postData = parameters.data(using: .utf8)

guard let url = URL(string: "\(Self.endpoint)/impression") else {
return
}

var request = URLRequest(url: url)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData

let task = session.dataTask(with: request)
task.resume()
}

}

private extension VideoTag {
var queryParameter: String {
switch self {
case .offVocal:
return "OFF_VOCAL"
case .withVocal:
return "WITH_VOCAL"
}
}
}
18 changes: 18 additions & 0 deletions YToke/Data/VideoStats/VideoStatsMutationProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// VideoStatsMutationProvider.swift
// YToke
//
// Created by Lyt on 9/14/20.
// Copyright © 2020 TestOrganization. All rights reserved.
//

import Foundation

/// Report video stats information to the backend
protocol VideoStatsMutationProvider {

func reportTag(videoId: String, tag: VideoTag)

func reportImpression(videoId: String, percentage: Double)

}
2 changes: 2 additions & 0 deletions YToke/DependencyContainer/DependencyContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct DataContainer {
let popUpAlertProvider: PopUpAlertProvider
let audioDevicesProvider: AudioDevicesProvider
let microphoneProvider: MicrophoneProvider
let videoStatsMutationProvider: VideoStatsMutationProvider
}

struct RepositoryContainer {
Expand All @@ -23,6 +24,7 @@ struct RepositoryContainer {
let privacyPermissionRepository: PrivacyPermissionRepository
let systemNavigator: SystemNavigator
let audioInputRepository: AudioInputRepository
let videoStatsRepository: VideoStatsRepository
}

protocol DependencyContainer {
Expand Down
11 changes: 8 additions & 3 deletions YToke/DependencyContainer/StandardDependencyContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ struct StandardDependencyContainer: DependencyContainer {

let data: DataContainer = {
DataContainer(videoStreamingProvider: XCDYoutubeVideoStreamingProvider(),
videoListProvider: InvidiousAPIVideoListProvider(),
videoListProvider: YTokeBackendVideoListProvider(),
avPrivacyPermissionProvider: MacOSAVPrivacyPermissionProvider(),
popUpAlertProvider: StandardPopUpAlertProvider(),
audioDevicesProvider: MacOSAudioDevicesProvider(),
microphoneProvider: AVAudioEngineMicrophoneProvider())
microphoneProvider: AVAudioEngineMicrophoneProvider(),
videoStatsMutationProvider: StandardVideoStatsMutationProvider())
}()

let repo: RepositoryContainer
Expand All @@ -36,11 +37,15 @@ struct StandardDependencyContainer: DependencyContainer {
microphoneProvider: data.microphoneProvider,
alertProvider: data.popUpAlertProvider,
privacyPermissionRepository: privacyPermissionRepository)

// swiftlint:disable:next line_length
let videoStatsRepository = StandardVideoStatsRepository(videoStatsMutationProvider: data.videoStatsMutationProvider)
repo = RepositoryContainer(videoStreamingRepository: videoStreamingRepository,
videoListRepository: videoListRepository,
privacyPermissionRepository: privacyPermissionRepository,
systemNavigator: MacOSSystemNavigator(),
audioInputRepository: audioInputRepository)
audioInputRepository: audioInputRepository,
videoStatsRepository: videoStatsRepository)
}

}
2 changes: 2 additions & 0 deletions YToke/Repository/VideoListRepository/Video.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ struct Video: Codable {
let id: String
let title: String
let thumbnail: URL?
let percentageFinished: Float?
let tag: [VideoTag]?
}

extension Video: Equatable {
Expand Down
14 changes: 14 additions & 0 deletions YToke/Repository/VideoListRepository/VideoTag.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// VideoTag.swift
// YToke
//
// Created by Lyt on 9/14/20.
// Copyright © 2020 TestOrganization. All rights reserved.
//

import Foundation

enum VideoTag: String, Codable {
case withVocal
case offVocal
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// StandardVideoStatsRepository.swift
// YToke
//
// Created by Lyt on 9/14/20.
// Copyright © 2020 TestOrganization. All rights reserved.
//

import Foundation

final class StandardVideoStatsRepository: VideoStatsRepository {

private let videoStatsMutationProvider: VideoStatsMutationProvider

init(videoStatsMutationProvider: VideoStatsMutationProvider) {
self.videoStatsMutationProvider = videoStatsMutationProvider
}

func reportTag(videoId: String, tag: VideoTag) {
videoStatsMutationProvider.reportTag(videoId: videoId, tag: tag)
}

func reportImpression(videoId: String, percentage: Double) {
videoStatsMutationProvider.reportImpression(videoId: videoId, percentage: percentage)
}

}
Loading

0 comments on commit dd192b0

Please sign in to comment.