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 Playlists concept. Ability to play audio and video in the backg…

…round and control it with ControlCenter.

Added Ability to detect videos on ANY website with generic code and get its decrypted URL.
Added Ability to play videos stream videos to other nearby devices (currently disabled).
Added some UI for testing the code works.
  • Loading branch information
Brandon-T committed Mar 25, 2020
commit f18c2754d48ba33b3911877d7b39202115e8f574
@@ -699,6 +699,9 @@
5E612A8F234B7FCA007D12B5 /* OnboardingRewardsAgreementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E1D8C6A232BF95200BDE662 /* OnboardingRewardsAgreementViewController.swift */; };
5E612A90234B7FCD007D12B5 /* OnboardingRewardsAgreementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E1D8C6C232BF9C200BDE662 /* OnboardingRewardsAgreementView.swift */; };
5E6683A923D61CF7005B3A6C /* NTPDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6683A823D61CF7005B3A6C /* NTPDownloader.swift */; };
5E6B9A3D239573D100E6720C /* Playlist.js in Resources */ = {isa = PBXBuildFile; fileRef = 5E6B9A3C239573D100E6720C /* Playlist.js */; };
5E6B9A402395752300E6720C /* PlaylistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6B9A3F2395752300E6720C /* PlaylistManager.swift */; };
5E6B9A4223957DB800E6720C /* PlaylistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6B9A4123957DB800E6720C /* PlaylistViewController.swift */; };
5E77F9A8236B362800E1649C /* DeviceCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E856FE7235E083B0094E113 /* DeviceCheck.swift */; };
5E77F9AA236B362E00E1649C /* Cryptography.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E856FE5235E08110094E113 /* Cryptography.swift */; };
5E77F9DD236BB68700E1649C /* AttestationDebugger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E77F9DC236BB68700E1649C /* AttestationDebugger.swift */; };
@@ -2195,6 +2198,9 @@
5E4845BF22DE381200372022 /* WindowRenderHelper.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = WindowRenderHelper.js; sourceTree = "<group>"; };
5E4845C122DE3DF800372022 /* WindowRenderHelperScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowRenderHelperScript.swift; sourceTree = "<group>"; };
5E6683A823D61CF7005B3A6C /* NTPDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NTPDownloader.swift; sourceTree = "<group>"; };
5E6B9A3C239573D100E6720C /* Playlist.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Playlist.js; sourceTree = "<group>"; };
5E6B9A3F2395752300E6720C /* PlaylistManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistManager.swift; sourceTree = "<group>"; };
5E6B9A4123957DB800E6720C /* PlaylistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistViewController.swift; sourceTree = "<group>"; };
5E77F9DC236BB68700E1649C /* AttestationDebugger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttestationDebugger.swift; sourceTree = "<group>"; };
5E856FE5235E08110094E113 /* Cryptography.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Cryptography.swift; sourceTree = "<group>"; tabWidth = 2; };
5E856FE7235E083B0094E113 /* DeviceCheck.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DeviceCheck.swift; sourceTree = "<group>"; tabWidth = 2; };
@@ -4391,6 +4397,7 @@
5E4845BF22DE381200372022 /* WindowRenderHelper.js */,
5EB57D0122FDC0CB00A07325 /* FullscreenHelper.js */,
F9B23EE123F61173000EB3D8 /* PaymentRequest.js */,
5E6B9A3C239573D100E6720C /* Playlist.js */,
);
path = UserScripts;
sourceTree = "<group>";
@@ -4542,6 +4549,7 @@
D0C95E35200FDC5400E4E51C /* MetadataParserHelper.swift */,
A9072B7F1D07B34100459960 /* NoImageModeHelper.swift */,
7BA8D1C61BA037F500C8AE9E /* OpenInHelper.swift */,
5E6B9A3F2395752300E6720C /* PlaylistManager.swift */,
D0C95E0D200FD3B200E4E51C /* PrintHelper.swift */,
D31CF65B1CC1959A001D0BD0 /* PrivilegedRequest.swift */,
FA6B2AC11D41F02D00429414 /* Punycode.swift */,
@@ -4572,6 +4580,7 @@
5E4845C122DE3DF800372022 /* WindowRenderHelperScript.swift */,
0A19365323508756002E2B81 /* LinkPreviewViewController.swift */,
0A66550923E9D9750047EF2A /* UserAgent.swift */,
5E6B9A4123957DB800E6720C /* PlaylistViewController.swift */,
);
indentWidth = 4;
path = Browser;
0A5E04FB23FEB53700E5A3E9 /* LaunchScreen.xcassets in Resources */,
0A0D3D3E21A4BE6C00BEE65B /* simple_malware.txt in Resources */,
0AA21E4B2302C4CC00358988 /* webauth_insert_key.json in Resources */,
5E6B9A3D239573D100E6720C /* Playlist.js in Resources */,
5DE5250D218CE92C000DFAA6 /* block-ads.json in Resources */,
F35B8D2B1D6380EA008E3D61 /* SessionRestore.html in Resources */,
5E4845C022DE381200372022 /* WindowRenderHelper.js in Resources */,
0B3E7D951B27A7CE00E2E84D /* AboutHomeHandler.swift in Sources */,
0AADC4CA20D2A66E00FDE368 /* FavoriteCell.swift in Sources */,
4422D4C021BFFB7600BF1855 /* options.cc in Sources */,
5E6B9A4223957DB800E6720C /* PlaylistViewController.swift in Sources */,
A198E75120C701ED00334C11 /* HistoryViewController.swift in Sources */,
D3BE7B461B054F8600641031 /* TestAppDelegate.swift in Sources */,
0A91598922B834CE00CCC119 /* BVC+Rewards.swift in Sources */,
D83822001FC7961D00303C12 /* DispatchQueueExtensions.swift in Sources */,
D03F8EB22004014E003C2224 /* FaviconHandler.swift in Sources */,
78A9D0962407B4A90077576C /* BraveGetUA.swift in Sources */,
5E6B9A402395752300E6720C /* PlaylistManager.swift in Sources */,
0AB2442C22AA789B00B4D9DD /* ReaderModeButton.swift in Sources */,
4422D55E21BFFB7F00BF1855 /* mimics_pcre.cc in Sources */,
0A6112AC230B00E7001BBC45 /* OnboardingNavigationController.swift in Sources */,
@@ -2116,6 +2116,8 @@ extension BrowserViewController: TabDelegate {

tab.addContentScript(WindowRenderHelperScript(tab: tab), name: WindowRenderHelperScript.name())

tab.addContentScript(PlaylistManager(tab: tab), name: PlaylistManager.name())

tab.addContentScript(RewardsReporting(rewards: rewards, tab: tab), name: RewardsReporting.name())
tab.addContentScript(AdsMediaReporting(rewards: rewards, tab: tab), name: AdsMediaReporting.name())

@@ -0,0 +1,125 @@
// 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
import WebKit
import Data
import BraveShared
import ObjectiveC.runtime

public protocol Disposable {
func dispose()
func bind(to: NSObject)
}

private class DisposableReference: Disposable {
private var associatedKey: Int
private var disposer: ((DisposableReference) -> Void)?

init(_ disposer: @escaping (DisposableReference) -> Void) {
associatedKey = 0
self.disposer = disposer
}

deinit {
dispose()
}

func dispose() {
self.disposer?(self)
self.disposer = nil
}

func bind(to object: NSObject) {
objc_setAssociatedObject(object, &associatedKey, self, .OBJC_ASSOCIATION_RETAIN)
}
}

public class Observable<T> {
public typealias Observer = (_ newValue: T, _ oldValue: T?) -> Void
private var subscribers = [(Observer, AnyObject)]()

public init(_ value: T) {
self.value = value
}

public var value: T {
didSet {
subscribers.forEach({
$0.0(value, oldValue)
})
}
}

@discardableResult
public func observe(_ observer: @escaping Observer) -> Disposable {
let disposable = DisposableReference({ [weak self] in self?.removeObserver($0) })
subscribers.append((observer, disposable))
subscribers.forEach { $0.0(value, nil) }
return disposable
}

public func removeObserver(_ object: AnyObject) {
subscribers = subscribers.filter { $0.1 !== object }
}

public func removeAllObservers() {
subscribers.removeAll()
}
}

struct PlaylistInfo: Decodable {
let name: String
let src: String

static func from(message: WKScriptMessage) throws -> PlaylistInfo? {
if !JSONSerialization.isValidJSONObject(message.body) {
return nil
}

let data = try JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted)
return try JSONDecoder().decode(PlaylistInfo.self, from: data)
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = (try? container.decode(String.self, forKey: .name)) ?? ""
self.src = (try? container.decode(String.self, forKey: .src)) ?? ""
}

private enum CodingKeys: String, CodingKey {
case name
case src
}
}

class PlaylistManager: TabContentScript {
fileprivate weak var tab: Tab?

init(tab: Tab) {
self.tab = tab
}

static func name() -> String {
return "PlaylistManager"
}

func scriptMessageHandlerName() -> String? {
return "playlistManager"
}

func userContentController(_ userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {

do {
guard let item = try PlaylistInfo.from(message: message) else { return }
if tab?.playlistItems.value.contains(where: { $0.src == item.src }) == false {
if !item.src.isEmpty {
tab?.playlistItems.value.append(item)
}
}
} catch {
print(error)
}
}
}
@@ -0,0 +1,139 @@
// 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
import UIKit
import BraveShared
import AVKit
import AVFoundation

class PlaylistViewController: UIViewController {
private var tabManager: TabManager
private var tableView = UITableView(frame: .zero, style: .grouped)
private var playlistItems = [PlaylistInfo]()

init(tabManager: TabManager) {
self.tabManager = tabManager
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()

tableView.register(PlaylistCell.self, forCellReuseIdentifier: "PlaylistCell")
tableView.dataSource = self
tableView.delegate = self

view.addSubview(tableView)
tableView.snp.makeConstraints({
$0.edges.equalTo(view.safeArea.edges)
})

tabManager.tabsForCurrentMode.forEach({
$0.playlistItems.observe { [weak self] _, _ in
guard let self = self else { return }
self.updateItems()
}.bind(to: self)
})
}

private func updateItems() {
playlistItems = tabManager.tabsForCurrentMode.map({ $0.playlistItems }).flatMap({ $0.value })
}

var player: AVPlayer?
}

extension PlaylistViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return playlistItems.count
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 45.0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "PlaylistCell", for: indexPath) as? PlaylistCell else {
return UITableViewCell()
}

cell.selectionStyle = .none
cell.titleLabel.text = playlistItems[indexPath.row].name
cell.playButton.setTitle("Play", for: .normal)
cell.playButton.tag = indexPath.row
cell.playButton.addTarget(self, action: #selector(onPlay(_:)), for: .touchUpInside)
return cell
}

@objc
private func onPlay(_ button: UIButton) {
let item = self.playlistItems[button.tag]
let player = AVPlayer(url: URL(string: item.src)!)

do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback, options: [.duckOthers])
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
UIApplication.shared.beginReceivingRemoteControlEvents()

let controller = AVPlayerViewController()
controller.player = player
present(controller, animated: true) {
player.play()
}

// self.player = player
// player.play()
} catch {
print(error)
}
}
}

extension PlaylistViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
return nil
}
}

private class PlaylistCell: UITableViewCell {
private let stackView = UIStackView().then {
$0.axis = .horizontal
}

let titleLabel = UILabel().then {
$0.font = UIFont.systemFont(ofSize: 12.0, weight: .medium)
$0.setContentHuggingPriority(.defaultLow, for: .horizontal)
}

let playButton = UIButton().then {
$0.setTitleColor(.blue, for: .normal)
$0.setContentHuggingPriority(.required, for: .horizontal)
}

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {

stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(playButton)

super.init(style: style, reuseIdentifier: reuseIdentifier)

contentView.addSubview(stackView)
stackView.snp.makeConstraints({
$0.edges.equalTo(contentView.snp.edges).inset(15.0)
})
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -142,6 +142,8 @@ class Tab: NSObject {
/// Any time a tab tries to make requests to display a Javascript Alert and we are not the active
/// tab instance, queue it for later until we become foregrounded.
fileprivate var alertQueue = [JSAlertInfo]()

var playlistItems = Observable<[PlaylistInfo]>([])

init(configuration: WKWebViewConfiguration, type: TabType = .regular) {
self.configuration = configuration
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.