Skip to content

Commit

Permalink
Added CloudKit override for live videos
Browse files Browse the repository at this point in the history
  • Loading branch information
insidegui committed May 20, 2017
1 parent 7a34f22 commit 60d3e48
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 11 deletions.
3 changes: 2 additions & 1 deletion CommunitySupport/CMSCommunityCenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ public final class CMSCommunityCenter: NSObject {
}
}

public func processNotification(userInfo: [String : Any]) {
public func processNotification(userInfo: [String : Any]) -> Bool {
// TODO: process CloudKit notification
return false
}

@objc private func createSubscriptionsIfNeeded() {
Expand Down
3 changes: 3 additions & 0 deletions ConfCore/SessionInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ public class SessionInstance: Object {
/// Whether this is being live streamed at the moment
public dynamic var isCurrentlyLive = false

/// Whether the live flag is being forced by an external source
public dynamic var isForcedLive = false

public override static func primaryKey() -> String? {
return "identifier"
}
Expand Down
8 changes: 0 additions & 8 deletions Fixtures/json_demo/videos_live.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
{
"updated": "2016-06-17T17:43:42-07:00",
"live_sessions": {
"101": {
"iosUrl": "http://devstreaming.apple.com/videos/wwdc/2016/live/mission_ghub2yon5yewl2i/hls_mvp.m3u8",
"tvosUrl": "http://devstreaming.apple.com/videos/wwdc/2016/live/mission_ghub2yon5yewl2i/atv_mvp.m3u8",
"actualEndDate": "2016-06-14T09:33:22-07:00",
"images": {
"shelf": "http://devstreaming.apple.com/videos/wwdc/2016/201h1g4asm31ti2l9n1/201/images/201_734x413.jpg"
}
},
"201": {
"iosUrl": "http://devstreaming.apple.com/videos/wwdc/2016/live/mission_ghub2yon5yewl2i/hls_mvp.m3u8",
"tvosUrl": "http://devstreaming.apple.com/videos/wwdc/2016/live/mission_ghub2yon5yewl2i/atv_mvp.m3u8",
Expand Down
4 changes: 4 additions & 0 deletions WWDC/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ final class AppCoordinator {
updateListsAfterSync()
}

func receiveNotification(with userInfo: [String : Any]) -> Bool {
return liveObserver.processSubscriptionNotification(with: userInfo)
}

// MARK: - State restoration

private var didRestoreLists = false
Expand Down
10 changes: 9 additions & 1 deletion WWDC/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
CMSCommunityCenter.shared.processNotification(userInfo: userInfo)
if CMSCommunityCenter.shared.processNotification(userInfo: userInfo) {
// Community center handled this notification
return
} else if coordinator.receiveNotification(with: userInfo) {
// Coordinator handled this notification
return
}

// handle other types of notification
}

}
Expand Down
2 changes: 1 addition & 1 deletion WWDC/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

struct Constants {

static let coreSchemaVersion: UInt64 = 12
static let coreSchemaVersion: UInt64 = 13
static let thumbnailHeight: CGFloat = 150

/// The relative position within the video the user must be before it is considered fully watched
Expand Down
122 changes: 122 additions & 0 deletions WWDC/LiveObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Foundation
import ConfCore
import RealmSwift
import CloudKit

final class LiveObserver {

Expand All @@ -19,14 +20,19 @@ final class LiveObserver {

var isRunning = false

private let specialEventsObserver: CloudKitLiveObserver

init(dateProvider: @escaping DateProvider, storage: Storage) {
self.dateProvider = dateProvider
self.storage = storage
self.specialEventsObserver = CloudKitLiveObserver(storage: storage)
}

func start() {
guard !isRunning else { return }

specialEventsObserver.fetch()

guard storage.realm.objects(Event.self).filter("startDate <= %@ AND endDate > %@ ", dateProvider(), dateProvider()).count > 0 else {
NSLog("Live event observer not started because we're not on WWDC week")
isRunning = false
Expand Down Expand Up @@ -73,6 +79,8 @@ final class LiveObserver {
try storage.realm.write {
// reset live flag for every instance
instances.forEach { instance in
guard !instance.isForcedLive else { return }

instance.isCurrentlyLive = value
}
}
Expand All @@ -81,4 +89,118 @@ final class LiveObserver {
}
}

func processSubscriptionNotification(with userInfo: [String : Any]) -> Bool {
return self.specialEventsObserver.processSubscriptionNotification(with: userInfo)
}

}

private extension SessionAsset {

static var recordType: String {
return "LiveSession"
}

convenience init?(record: CKRecord) {
guard let number = record["sessionNumber"] as? Int else { return nil }
guard let hls = record["hls"] as? String else { return nil }

self.init()

self.assetType = .liveStreamVideo
self.sessionId = "\(number)"
self.remoteURL = hls
self.year = Calendar.current.component(.year, from: Today())
}

}

private final class CloudKitLiveObserver: NSObject {

private let storage: Storage

private lazy var database: CKDatabase = CKContainer.default().publicCloudDatabase

init(storage: Storage) {
self.storage = storage

super.init()
}

func fetch() {
let query = CKQuery(recordType: SessionAsset.recordType, predicate: NSPredicate(value: true))
let operation = CKQueryOperation(query: query)

var fetchedRecords: [CKRecord] = []

operation.recordFetchedBlock = { record in
fetchedRecords.append(record)
}

operation.queryCompletionBlock = { [unowned self] _, error in
if let error = error {
NSLog("Error fetching special live videos: \(error)")
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.fetch()
}
} else {
DispatchQueue.main.async { self.store(fetchedRecords) }
}
}

database.add(operation)

subscribeIfNeeded()
}

private func subscribeIfNeeded() {
guard subscriptionID == nil else { return }

let options: CKQuerySubscriptionOptions = [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion]
let subscription = CKQuerySubscription(recordType: SessionAsset.recordType,
predicate: NSPredicate(value: true),
options: options)

database.save(subscription) { [unowned self] savedSubscription, error in
self.subscriptionID = savedSubscription?.subscriptionID
}
}

private let subscriptionDefaultsKey = "specialEventsLiveObserverCKSubscriptionID"

private var subscriptionID: String? {
get {
return UserDefaults.standard.object(forKey: subscriptionDefaultsKey) as? String
}
set {
UserDefaults.standard.set(newValue, forKey: subscriptionDefaultsKey)
}
}

func processSubscriptionNotification(with userInfo: [String : Any]) -> Bool {
let notification = CKNotification(fromRemoteNotificationDictionary: userInfo)

// check if the remote notification is for us, if not, tell the caller that we haven't handled it
guard notification.subscriptionID == self.subscriptionID else { return false }

// notification for special live events, just fetch everything again
fetch()

return true
}

private func store(_ records: [CKRecord]) {
print("storing live records")

storage.unmanagedUpdate { [unowned self] realm in
records.forEach { record in
guard let asset = SessionAsset(record: record) else { return }
guard let instance = self.storage.session(with: "\(asset.year)-\(asset.sessionId)")?.instances.first else { return }

instance.isForcedLive = (record["isLive"] as? Int == 1)
instance.isCurrentlyLive = instance.isForcedLive
}
}
}

}

0 comments on commit 60d3e48

Please sign in to comment.