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

clear playlistItems when navigating on YT #2587

Open
wants to merge 14 commits into
base: feature/Playlists
from
Open
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Added playing from cache, ability to detect multiple video sources, a…

…udio, mime-type detection and playing from Data-URIs.
  • Loading branch information
Brandon-T committed Apr 9, 2020
commit ec34cfc1ef04eeac8aab5ae3a4647b51d10a0039
@@ -688,6 +688,7 @@
5E2DF91D24194779000A8943 /* PlaylistCacheLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2DF91C24194779000A8943 /* PlaylistCacheLoader.swift */; };
5E3477E922D7771700B0D5F8 /* ResourceDownloader.js in Resources */ = {isa = PBXBuildFile; fileRef = 5E3477E822D7771700B0D5F8 /* ResourceDownloader.js */; };
5E34781022D7A1D200B0D5F8 /* ResourceDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E34780F22D7A1D200B0D5F8 /* ResourceDownloadManager.swift */; };
5E3B876E243F0D79009BA5F5 /* DataURIParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B876D243F0D79009BA5F5 /* DataURIParser.swift */; };
5E46C371234FACC600ACA8C1 /* root.cer in Resources */ = {isa = PBXBuildFile; fileRef = 5E46C36D234FACC600ACA8C1 /* root.cer */; };
5E46C372234FACC600ACA8C1 /* leaf.cer in Resources */ = {isa = PBXBuildFile; fileRef = 5E46C36E234FACC600ACA8C1 /* leaf.cer */; };
5E46C373234FACC600ACA8C1 /* intermediate.cer in Resources */ = {isa = PBXBuildFile; fileRef = 5E46C36F234FACC600ACA8C1 /* intermediate.cer */; };
@@ -1375,9 +1376,9 @@
0A5922F32357863000959DD1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
0A5CBD3323D4677100362CC8 /* NTPLearnMoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NTPLearnMoreViewController.swift; sourceTree = "<group>"; };
0A5CBD3723D4905D00362CC8 /* NTPNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NTPNotificationView.swift; sourceTree = "<group>"; };
0A60A1882358AF9E00953CA8 /* Brave.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Brave.xctestplan; path = Client.xcodeproj/Brave.xctestplan; sourceTree = "<group>"; };
0A5E04F823FEADA800E5A3E9 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
0A5E04FA23FEB53700E5A3E9 /* LaunchScreen.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = LaunchScreen.xcassets; sourceTree = "<group>"; };
0A60A1882358AF9E00953CA8 /* Brave.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Brave.xctestplan; path = Client.xcodeproj/Brave.xctestplan; sourceTree = "<group>"; };
0A6112AB230B00E7001BBC45 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = "<group>"; };
0A6112BC230B4306001BBC45 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
0A62A354238C6F3E008902E3 /* Bookmark+Restoration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+Restoration.swift"; sourceTree = "<group>"; };
@@ -2201,6 +2202,7 @@
5E2DF91C24194779000A8943 /* PlaylistCacheLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistCacheLoader.swift; sourceTree = "<group>"; };
5E3477E822D7771700B0D5F8 /* ResourceDownloader.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = ResourceDownloader.js; sourceTree = "<group>"; };
5E34780F22D7A1D200B0D5F8 /* ResourceDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceDownloadManager.swift; sourceTree = "<group>"; };
5E3B876D243F0D79009BA5F5 /* DataURIParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataURIParser.swift; sourceTree = "<group>"; };
5E46C36D234FACC600ACA8C1 /* root.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = root.cer; sourceTree = "<group>"; };
5E46C36E234FACC600ACA8C1 /* leaf.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = leaf.cer; sourceTree = "<group>"; };
5E46C36F234FACC600ACA8C1 /* intermediate.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = intermediate.cer; sourceTree = "<group>"; };
@@ -4289,6 +4291,7 @@
5E66CCB72432588B0076BA78 /* VideoPlayer.swift */,
5E66CCBA243259390076BA78 /* PlaylistNowPlayingBar.swift */,
5E66CCBC24336D2D0076BA78 /* PlaylistMultipleSelectionController.swift */,
5E3B876D243F0D79009BA5F5 /* DataURIParser.swift */,
);
path = Playlists;
sourceTree = "<group>";
D0C95E36200FDC5500E4E51C /* MetadataParserHelper.swift in Sources */,
4422D55B21BFFB7F00BF1855 /* onepass.cc in Sources */,
0A3C78A1230597DA0022F6D8 /* OnboardingShieldsViewController.swift in Sources */,
A1CDF22D20BDDB66005C6E58 /* BasicAnimationController.swift in Sources */,
5E8B486923983BFC0075A4EF /* CarplayMediaManager.swift in Sources */,
0BF1B7E31AC60DEA00A7B407 /* InsetButton.swift in Sources */,
D0C95EF6201A55A800E4E51C /* BrowserViewController+UIDropInteractionDelegate.swift in Sources */,
4422D50721BFFB7600BF1855 /* port_posix.cc in Sources */,
E64ED8FA1BC55AE300DAF864 /* UIAlertControllerExtensions.swift in Sources */,
A16DC67F20E585D90069C8E1 /* PasscodeSettingsViewController.swift in Sources */,
A1D8420320BC44F800BDAFF7 /* PopoverContainerView.swift in Sources */,
5ED3F38F239AD9A30048CE56 /* PlaylistItem+CoreDataClass.swift in Sources */,
4422D56221BFFB7F00BF1855 /* dfa.cc in Sources */,
A1AD4BE120C082EF007A6EA1 /* UIGestureRecognizerExtensions.swift in Sources */,
F84B22041A0910F600AAB793 /* AppDelegate.swift in Sources */,
0A7B5D6722689C5D00AADF22 /* AddEditHeaderView.swift in Sources */,
F930CDD12270F09000A23FE1 /* WebAuthnAuthenticateRequest.swift in Sources */,
5E3B876E243F0D79009BA5F5 /* DataURIParser.swift in Sources */,
D8C75DF3207584C400BB8AD0 /* UIImageViewAligned.m in Sources */,
E653422D1C5944F90039DD9E /* BrowserPrompts.swift in Sources */,
4422D43621BFD29E00BF1855 /* NSFileManager+Tar.m in Sources */,
@@ -3516,8 +3516,16 @@ extension BrowserViewController: OnboardingControllerDelegate {

extension BrowserViewController: NowPlayingBarDelegate {
func onAddToPlaylist() {
let controller = PlaylistMultipleSelectionController(tabManager: self.tabManager)
self.present(controller, animated: true, completion: nil)
if let tab = tabManager.selectedTab {
let items = tab.playlistItems.value
if items.count > 1 {
let controller = PlaylistMultipleSelectionController(tabManager: self.tabManager)
self.present(controller, animated: true, completion: nil)
} else {
let controller = UINavigationController(rootViewController: PlaylistViewController(tabManager: self.tabManager))
self.present(controller, animated: true, completion: nil)
}
}
}

func onExpand() {
@@ -122,6 +122,7 @@ extension BrowserViewController: WKNavigationDelegate {
}

if url.scheme == "about" {
self.tabManager.tabForWebView(webView)?.playlistItems.value = []
decisionHandler(.allow)
return
}
@@ -258,6 +259,7 @@ extension BrowserViewController: WKNavigationDelegate {
self.tabManager.selectedTab?.alertShownCount = 0
self.tabManager.selectedTab?.blockAllAlerts = false
}
self.tabManager.tabForWebView(webView)?.playlistItems.value = []
decisionHandler(.allow)
return
}
@@ -137,7 +137,7 @@ class PlaylistManager: TabContentScript {
do {
guard let item = try PlaylistInfo.from(message: message) else { return }
if !Playlist.shared.itemExists(item: item) {
if let items = tab?.playlistItems, let index = items.value.firstIndex(where: { $0.pageSrc == item.pageSrc }) {
if let items = tab?.playlistItems, let index = items.value.firstIndex(where: { $0.src == item.src }) {
if items.value[index].duration < 0.01 {
items.value[index].duration = item.duration
items.refresh()
@@ -0,0 +1,36 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import Foundation

class DataURIParser {
let mediaType: String
let headers: [String]
let data: Data

init(uri: String) throws {
if uri == "data:," {
mediaType = "text/plain"
headers = ["charset=US-ASCII"]
data = Data()
return
}

if !uri.lowercased().hasPrefix("data:") {
throw "Invalid Data-URI"
}

guard let infoSegment = uri.firstIndex(of: ",") else {
throw "Invalid Data-URI"
}

let startSegment = uri.index(uri.startIndex, offsetBy: "data:".count)
self.headers = uri[startSegment..<infoSegment].split(separator: ";").map({ String($0) })
mediaType = headers.first ?? "text/plain"

let dataSegment = uri.index(infoSegment, offsetBy: 1)
//data = try! Data(contentsOf: URL(string: uri)!)
data = Data(base64Encoded: String(uri[dataSegment..<uri.endIndex])) ?? Data()
}
}
@@ -25,7 +25,7 @@ class Playlist {
playlistItem.pageTitle = item.pageTitle
playlistItem.pageSrc = item.pageSrc
playlistItem.dateAdded = Date()
playlistItem.cachedData = Data()
playlistItem.cachedData = (try? Data(contentsOf: URL(string: item.src)!)) ?? Data()
playlistItem.duration = item.duration

self.saveContext(self.backgroundContext)
@@ -39,15 +39,44 @@ class Playlist {

func removeItem(item: PlaylistInfo) {
if !self.itemExists(item: item) {
self.backgroundContext.perform { [weak self] in
self.backgroundContext.performAndWait { [weak self] in
guard let self = self else { return }
let request = { () -> NSBatchDeleteRequest in
let request: NSFetchRequest<NSFetchRequestResult> = PlaylistItem.fetchRequest()
request.predicate = NSPredicate(format: "pageSrc == %@", item.pageSrc)

let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
deleteRequest.resultType = .resultTypeObjectIDs
return deleteRequest
}()

let hash = PlaylistItem(context: self.backgroundContext)
if let result = (try? self.backgroundContext.execute(request)) as? NSBatchDeleteResult {
if let deletedObjects = result.result as? [NSManagedObjectID] {
NSManagedObjectContext.mergeChanges(
fromRemoteContextSave: [NSDeletedObjectsKey: deletedObjects],
into: [self.mainContext, self.backgroundContext]
)
}
} else {
let request: NSFetchRequest<PlaylistItem> = PlaylistItem.fetchRequest()
request.predicate = NSPredicate(format: "pageSrc == %@", item.pageSrc)

(try? self.backgroundContext.fetch(request))?.forEach({
self.backgroundContext.delete($0)
})
}

self.saveContext(self.backgroundContext)
self.backgroundContext.reset()
}
}
}

func removeAll() {
self.destroy()
self.persistentContainer = self.create()
}

func getItems() -> [PlaylistInfo] {
let request: NSFetchRequest<PlaylistItem> = PlaylistItem.fetchRequest()
return (try? self.mainContext.fetch(request))?.map({
@@ -7,54 +7,152 @@ import AVFoundation
import WebKit
import MobileCoreServices

private class MimeTypeDetector {
private(set) var mimeType: String = ""
private(set) var fileExtension: String = ""

init(data: Data) {
let bytes = [UInt8](data)

if scan(data: bytes, header: [0x1A, 0x45, 0xDF, 0xA3]) {
mimeType = "video/webm"
fileExtension = "webm"
return
}

if scan(data: bytes, header: [0x4F, 0x67, 0x67, 0x53]) {
mimeType = "application/ogg"
fileExtension = "ogg"
return
}

if scan(data: bytes, header: [0x52, 0x49, 0x46, 0x46]) {
if [UInt8](data.subdata(in: 4..<5))[0] == 57 {
mimeType = "audio/x-wav"
fileExtension = "wav"
} else {
mimeType = "video/x-msvideo"
fileExtension = "avi"
}
return
}

if scan(data: bytes, header: [0xFF, 0xFB]) || scan(data: bytes, header: [0x49, 0x44, 0x33]) {
mimeType = "audio/mpeg"
fileExtension = "mp4"
return
}

if scan(data: bytes, header: [0x49, 0x44, 0x33]) {
mimeType = "audio/mpeg"
fileExtension = "mp4"
return
}

if scan(data: bytes, header: [0x66, 0x4C, 0x61, 0x43]) {
mimeType = "audio/flac"
fileExtension = "flac"
return
}

if scan(data: [UInt8](bytes[4..<bytes.count]), header: [0x66, 0x74, 0x79, 0x70, 0x4D, 0x53, 0x4E, 0x56]) {
mimeType = "video/mp4"
fileExtension = "mp4"
return
}

if scan(data: [UInt8](bytes[4..<bytes.count]), header: [0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D]) {
mimeType = "video/mp4"
fileExtension = "mp4"
return
}

if scan(data: [UInt8](bytes[4..<bytes.count]), header: [0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32]) {
mimeType = "video/mp4"
fileExtension = "mp4"
return
}

mimeType = "application/x-mpegURL"
fileExtension = "mpg"
}

private func scan(data: [UInt8], header: [UInt8]) -> Bool {
if data.count < header.count {
return false
}

for i in 0..<header.count {
if data[i] != header[i] {
return false
}
}
return true
}
}

class PlaylistCacheLoader: NSObject, AVAssetResourceLoaderDelegate, URLSessionTaskDelegate {

private lazy var session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: .main)
private var requests = [URL: AVAssetResourceLoadingRequest]()
private var tasks = [URL: URLSessionDataTask]()
//private lazy var session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: .main)
private var requests = Set<AVAssetResourceLoadingRequest>()
private var cacheData = Data()
private(set) var mimeType = String()

override init() {
super.init()
}

init(cacheData: Data) {
self.cacheData = cacheData
init(cacheData: Data, mimeType: String? = nil) {
super.init()

self.cacheData = cacheData
self.mimeType = mimeType ?? MimeTypeDetector(data: cacheData).mimeType
}

func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {

if let url = loadingRequest.request.url {
if !cacheData.isEmpty {
loadingRequest.dataRequest?.respond(with: cacheData)
loadingRequest.finishLoading()
return true
}
requests.insert(loadingRequest)
processPendingRequests()
return true
}

func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
requests.remove(loadingRequest)
}

func processPendingRequests() {
let requestsFulfilled = Set<AVAssetResourceLoadingRequest>(requests.compactMap {
$0.contentInformationRequest?.contentType = self.mimeType
$0.contentInformationRequest?.contentLength = Int64(self.cacheData.count)
$0.contentInformationRequest?.isByteRangeAccessSupported = true

let cache = Playlist.shared.getCache(item: PlaylistInfo(name: "", src: url.absoluteString, pageSrc: url.absoluteString, pageTitle: "", duration: 0.0))
if !cache.isEmpty {
loadingRequest.dataRequest?.respond(with: cache)
loadingRequest.finishLoading()
return true
if self.haveEnoughDataToFulfillRequest($0.dataRequest!) {
$0.finishLoading()
return $0
}

requests.updateValue(loadingRequest, forKey: url)
let task = session.dataTask(with: loadingRequest.request)
tasks.updateValue(task, forKey: url)
task.resume()
return true
}
return nil
})

_ = requestsFulfilled.map { self.requests.remove($0) }

return false
}

private func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(.allow)
func haveEnoughDataToFulfillRequest(_ dataRequest: AVAssetResourceLoadingDataRequest) -> Bool {
let requestedOffset = Int(dataRequest.requestedOffset)
let currentOffset = Int(dataRequest.currentOffset)
let requestedLength = dataRequest.requestedLength

if currentOffset <= cacheData.count {
let bytesToRespond = min(cacheData.count - currentOffset, requestedLength)
let data = cacheData.subdata(in: Range(uncheckedBounds: (currentOffset, currentOffset + bytesToRespond)))
dataRequest.respond(with: data)
return cacheData.count >= requestedLength + requestedOffset
}

return false
}

private func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let request = dataTask.originalRequest, let url = request.url, let dataRequest = requests[url]?.dataRequest {
/*if let request = dataTask.originalRequest, let url = request.url, let dataRequest = requests[url]?.dataRequest {
let neededData = dataRequest.requestedLength - Int(dataRequest.currentOffset)
if data.count >= neededData {
if let contentInformationRequest = requests[url]?.contentInformationRequest, let mimeType = dataTask.response?.mimeType {
@@ -71,13 +169,7 @@ class PlaylistCacheLoader: NSObject, AVAssetResourceLoaderDelegate, URLSessionTa
} else {
dataRequest.respond(with: data)
}
}
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let request = task.originalRequest, let url = request.url, let dataRequest = requests[url] {
dataRequest.finishLoading(with: error)
}
}*/
}
}

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.