Skip to content
New issue

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

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

Already on GitHub? # to your account

Track upload statistics #3

Merged
merged 10 commits into from
Mar 27, 2023
14 changes: 0 additions & 14 deletions Package.resolved

This file was deleted.

4 changes: 1 addition & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ let package = Package(
targets: ["MuxUploadSDK"]
),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
],
dependencies: [ ],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
Expand Down
60 changes: 60 additions & 0 deletions Sources/MuxUploadSDK/Internal Utilities/Reporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// Reporter.swift
//
//
// Created by Liam Lindner on 3/16/23.
//

import Foundation

class Reporter: NSObject {
var session: URLSession?
var pendingUploadEvent: UploadEvent?

override init() {
super.init()

let sessionConfig: URLSessionConfiguration = URLSessionConfiguration.default
session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
}

func report(startTime: TimeInterval, endTime: TimeInterval, fileSize: UInt64, videoDuration: Double) -> Void {
self.pendingUploadEvent = UploadEvent(startTime: startTime, endTime: endTime, fileSize: fileSize, videoDuration: videoDuration)

let request = self.generateRequest(url: URL(string: "https://mobile.muxanalytics.com")!)

let dataTask = session?.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
self.pendingUploadEvent = nil
})
dataTask?.resume()
}

private func generateRequest(url: URL) -> URLRequest {
let request = NSMutableURLRequest(url: url,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

do {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = JSONEncoder.KeyEncodingStrategy.convertToSnakeCase
let jsonData = try encoder.encode(self.pendingUploadEvent)
request.httpBody = jsonData
} catch _ as NSError {}

return request as URLRequest
}
}

extension Reporter: URLSessionDelegate, URLSessionTaskDelegate {
public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Swift.Void) {
if(self.pendingUploadEvent != nil) {
if let redirectUrl = request.url {
let request = self.generateRequest(url: redirectUrl)
completionHandler(request)
}
}
}
}
65 changes: 65 additions & 0 deletions Sources/MuxUploadSDK/Internal Utilities/UploadEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// UploadEvent.swift
//
//
// Created by Liam Lindner on 3/22/23.
//

import Foundation
import UIKit

struct UploadEvent: Codable {
var type = "upload"
var startTime: TimeInterval
var endTime: TimeInterval
var fileSize: UInt64
var videoDuration: Double

var sdkVersion: String

var osName: String
var osVersion: String

var deviceModel: String

var appName: String?
var appVersion: String?

var regionCode: String?

init(startTime: TimeInterval, endTime: TimeInterval, fileSize: UInt64, videoDuration: Double) {
let locale = Locale.current
let device = UIDevice.current

self.startTime = startTime
self.endTime = endTime
self.fileSize = fileSize
self.videoDuration = videoDuration

self.sdkVersion = Version.versionString

self.osName = device.systemName
self.osVersion = device.systemVersion

self.deviceModel = device.model

self.appName = Bundle.main.appName
self.appVersion = Bundle.main.appVersion

if #available(iOS 16, *) {
self.regionCode = locale.language.region?.identifier
} else {
self.regionCode = locale.regionCode
}
}
}

extension Bundle {
var appName: String? {
return object(forInfoDictionaryKey: "CFBundleName") as? String
}

var appVersion: String? {
return object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
}
}
4 changes: 2 additions & 2 deletions Sources/MuxUploadSDK/Public API/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public struct Version {
/// Major version.
public static let major = 0
/// Minor version.
public static let minor = 1
public static let minor = 2
/// Revision number.
public static let revision = 0
public static let revision = 1

/// String form of the version number.
public static let versionString = "\(major).\(minor).\(revision)"
Expand Down
23 changes: 22 additions & 1 deletion Sources/MuxUploadSDK/Upload/ChunkedFileUploader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Emily Dixon on 2/23/23.
//

import AVFoundation
import Foundation

/// Uploads a file in chunks according to the input configuration. Retries are handled according to the input ``UploadInfo``
Expand All @@ -23,6 +24,7 @@ class ChunkedFileUploader {
private var overallProgress: Progress = Progress()
private var lastSeenUpdate: InternalUploadState? = nil
private var lastByte: UInt64 = 0
private let reporter = Reporter()

func addDelegate(withToken token: Int, _ delegate: ChunkedFileUploaderDelegate) {
delegates.updateValue(delegate, forKey: token)
Expand Down Expand Up @@ -88,15 +90,34 @@ class ChunkedFileUploader {
do {
// It's fine if it's already open, that's handled by ignoring the call
try file.openFile(fileURL: uploadInfo.videoFile)
let fileSize = file.fileSize
try file.seekTo(byte: lastByte)
let result = try await makeWorker().performUpload()
file.close()

let success = UploadResult(
finalProgress: result.progress,
startTime: result.startTime,
finishTime: result.updateTime
)

let asset = AVAsset(url: uploadInfo.videoFile)

var duration: CMTime
if #available(iOS 15, *) {
duration = try await asset.load(.duration)
} else {
await asset.loadValues(forKeys: ["duration"])
duration = asset.duration
}

reporter.report(
startTime: success.startTime,
endTime: success.finishTime,
fileSize: fileSize,
videoDuration: duration.seconds
)

notifyStateFromWorker(.success(success))
} catch {
file.close()
Expand Down