Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
Show youtube redirect when no playable video
Browse files Browse the repository at this point in the history
  • Loading branch information
Akiva Leffert authored and aleffert committed Mar 16, 2016
1 parent 8e2e765 commit b97189f
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 34 deletions.
6 changes: 4 additions & 2 deletions Source/OEXVideoEncoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ NS_ASSUME_NONNULL_BEGIN

@interface OEXVideoEncoding : NSObject

- (id)initWithDictionary:(NSDictionary*)dictionary;
- (id)initWithURL:(NSString*)URL size:(NSNumber*)size;
- (id)initWithDictionary:(NSDictionary*)dictionary name:(NSString*)name;
- (id)initWithName:(nullable NSString*)name URL:(NSString*)URL size:(NSNumber*)size;

@property (readonly, nonatomic, copy, nullable) NSString* name;
@property (readonly, nonatomic, copy, nullable) NSString* URL;
@property (readonly, nonatomic, strong, nullable) NSNumber* size;
@property (readonly, nonatomic) BOOL isYoutube;

/// [String], ordered by preference
+ (NSArray*)knownEncodingNames;
Expand Down
17 changes: 14 additions & 3 deletions Source/OEXVideoEncoding.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@

#import "OEXVideoEncoding.h"

static NSString* const OEXVideoEncodingYoutube = @"youtube";
static NSString* const OEXVideoEncodingMobileHigh = @"mobile_high";
static NSString* const OEXVideoEncodingMobileLow = @"mobile_low";

@interface OEXVideoEncoding ()

@property (copy, nonatomic) NSString* name;
@property (copy, nonatomic) NSString* URL;
@property (strong, nonatomic) NSNumber* size;

Expand All @@ -18,15 +23,16 @@ @interface OEXVideoEncoding ()
@implementation OEXVideoEncoding

+ (NSArray*)knownEncodingNames {
return @[@"mobile_low", @"mobile_high"];
return @[OEXVideoEncodingMobileLow, OEXVideoEncodingMobileHigh, OEXVideoEncodingYoutube];
}

+ (NSString*)fallbackEncodingName {
return @"fallback";
}

- (id)initWithDictionary:(NSDictionary*)dictionary {
- (id)initWithDictionary:(NSDictionary*)dictionary name:(NSString*)name {
if(self != nil) {
self.name = name;
self.URL = dictionary[@"url"];
self.size = dictionary[@"file_size"];
}
Expand All @@ -35,13 +41,18 @@ - (id)initWithDictionary:(NSDictionary*)dictionary {
}


- (id)initWithURL:(NSString*)URL size:(NSNumber*)size {
- (id)initWithName:(NSString*)name URL:(NSString*)URL size:(NSNumber*)size {
self = [super init];
if(self != nil) {
self.name = name;
self.URL = URL;
self.size = size;
}
return self;
}

- (BOOL)isYoutube {
return [self.name isEqualToString:OEXVideoEncodingYoutube];
}

@end
8 changes: 6 additions & 2 deletions Source/OEXVideoSummary.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

NS_ASSUME_NONNULL_BEGIN

@class OEXVideoEncoding;
@class OEXVideoPathEntry;

@interface OEXVideoSummary : NSObject
Expand All @@ -19,14 +20,17 @@ NS_ASSUME_NONNULL_BEGIN
- (id)initWithDictionary:(NSDictionary*)dictionary videoID:(NSString*)videoID name:(NSString*)name;

/// Generate a simple stub video summary. Used only for testing
/// path : OEXVideoPathEntry array
- (id)initWithVideoID:(NSString*)videoID name:(NSString*)name path:(NSArray*)path;
- (id)initWithVideoID:(NSString*)videoID name:(NSString*)name path:(NSArray<OEXVideoPathEntry*>*)path;
/// Generate a simple stub video summary. Used only for testing
- (id)initWithVideoID:(NSString*)videoID name:(NSString*)name encodings:(NSDictionary<NSString*, OEXVideoEncoding *> *)encodings;

@property (readonly, nonatomic, copy, nullable) NSString* sectionURL; // used for OPEN IN BROWSER

@property (readonly, strong, nonatomic, nullable) OEXVideoPathEntry* chapterPathEntry;
@property (readonly, strong, nonatomic, nullable) OEXVideoPathEntry* sectionPathEntry;

@property (readonly, nonatomic, strong, nullable) OEXVideoEncoding* preferredEncoding;

/// displayPath : OEXVideoPathEntry array
/// This is just the list [chapterPathEntry, sectionPathEntry], filtering out nil items
@property (readonly, copy, nonatomic, nullable) NSArray* displayPath;
Expand Down
36 changes: 25 additions & 11 deletions Source/OEXVideoSummary.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "edX-Swift.h"
#import "OEXVideoEncoding.h"
#import "OEXVideoPathEntry.h"
#import "NSMutableDictionary+OEXSafeAccess.h"
#import "NSArray+OEXFunctional.h"
#import "NSArray+OEXSafeAccess.h"
#import "NSMutableDictionary+OEXSafeAccess.h"
Expand All @@ -34,7 +35,6 @@ @interface OEXVideoSummary ()

// [String:OEXVideoEncoding]
@property (nonatomic, strong) NSDictionary* encodings;
@property (nonatomic, strong) OEXVideoEncoding* preferredEncoding;

@property (nonatomic, strong) NSDictionary* transcripts;

Expand Down Expand Up @@ -74,18 +74,10 @@ - (id)initWithDictionary:(NSDictionary*)dictionary {
NSDictionary* rawEncodings = OEXSafeCastAsClass(summary[@"encoded_videos"], NSDictionary);
NSMutableDictionary* encodings = [[NSMutableDictionary alloc] init];
[rawEncodings enumerateKeysAndObjectsUsingBlock:^(NSString* name, NSDictionary* encodingInfo, BOOL *stop) {
OEXVideoEncoding* encoding = [[OEXVideoEncoding alloc] initWithDictionary:encodingInfo];
OEXVideoEncoding* encoding = [[OEXVideoEncoding alloc] initWithDictionary:encodingInfo name:name];
[encodings safeSetObject:encoding forKey:name];
}];
self.encodings = (rawEncodings != nil) ? encodings : @{@"fallback" : [[OEXVideoEncoding alloc] initWithURL: videoURL size:videoSize]};

for(NSString* name in [[OEXVideoEncoding knownEncodingNames] arrayByAddingObject:[OEXVideoEncoding fallbackEncodingName]]) {
OEXVideoEncoding* encoding = self.encodings[name];
if (encoding != nil) {
self.preferredEncoding = encoding;
break;
}
}
self.encodings = (rawEncodings != nil) ? encodings : @{@"fallback" : [[OEXVideoEncoding alloc] initWithName:nil URL:videoURL size:videoSize]};

self.videoThumbnailURL = [summary objectForKey:@"video_thumbnail_url"];
self.videoID = [summary objectForKey:@"id"] ;
Expand Down Expand Up @@ -119,6 +111,28 @@ - (id)initWithVideoID:(NSString*)videoID name:(NSString*)name path:(NSArray*)pat
return self;
}

- (id)initWithVideoID:(NSString *)videoID name:(NSString *)name encodings:(NSDictionary<NSString*, OEXVideoEncoding *> *)encodings {
self = [super init];
if(self != nil) {
self.name = name;
self.videoID = videoID;
self.encodings = encodings;
}
return self;
}

- (OEXVideoEncoding*)preferredEncoding {
for(NSString* name in [[OEXVideoEncoding knownEncodingNames] arrayByAddingObject:[OEXVideoEncoding fallbackEncodingName]]) {
OEXVideoEncoding* encoding = self.encodings[name];
if (encoding != nil) {
return encoding;
}
}
// Don't have a known encoding, so just pick one. These are in a dict, but we need to do
// something stable, so just do it alphabetically
return self.encodings[[self.encodings.allKeys sortedArrayUsingSelector:@selector(compare:)].firstObject];
}

- (NSString*)videoURL {
return self.preferredEncoding.URL;
}
Expand Down
41 changes: 26 additions & 15 deletions Source/VideoBlockViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@ class VideoBlockViewController : UIViewController, CourseBlockViewController, OE
func addLoadListener() {
loader.listen (self,
success : { [weak self] block in
if let video = self?.environment.interface?.stateForVideoWithID(self?.blockID, courseID : self?.courseID) {
if let video = block.type.asVideo,
let encoding = video.preferredEncoding where encoding.isYoutube,
let URL = encoding.URL
{
self?.showYoutubeMessage(URL)
}
else if
let video = self?.environment.interface?.stateForVideoWithID(self?.blockID, courseID : self?.courseID)
where block.type.asVideo?.preferredEncoding != nil
{
self?.showLoadedBlock(block, forVideo: video)
}
else {
Expand Down Expand Up @@ -215,24 +224,26 @@ class VideoBlockViewController : UIViewController, CourseBlockViewController, OE
private func showError(error : NSError?) {
loadController.state = LoadState.failed(error, icon: .UnknownError, message: Strings.videoContentNotAvailable)
}

private func showLoadedBlock(block : CourseBlock, forVideo video: OEXHelperVideoDownload?) {
if let _ = block.type.asVideo {
navigationItem.title = block.displayName

dispatch_async(dispatch_get_main_queue()) {
self.loadController.state = .Loaded
}

if let video = video {
videoController.playVideoFor(video)

private func showYoutubeMessage(URL: String) {
let buttonInfo = MessageButtonInfo(title: Strings.Video.viewOnYoutube) {
if let URL = NSURL(string: URL) {
UIApplication.sharedApplication().openURL(URL)
}
}
else {
showError(nil)
}
loadController.state = LoadState.empty(icon: .CourseModeVideo, message: Strings.Video.onlyOnYoutube, attributedMessage: nil, accessibilityMessage: nil, buttonInfo: buttonInfo)
}

private func showLoadedBlock(block : CourseBlock, forVideo video: OEXHelperVideoDownload) {
navigationItem.title = block.displayName

dispatch_async(dispatch_get_main_queue()) {
self.loadController.state = .Loaded
}

videoController.playVideoFor(video)
}

private func canDownloadVideo() -> Bool {
let hasWifi = environment.reachability.isReachableViaWiFi() ?? false
let onlyOnWifi = environment.dataManager.interface?.shouldDownloadOnlyOnWifi ?? false
Expand Down
6 changes: 5 additions & 1 deletion Source/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,12 @@
"VIDEO_ONLY_ON_WEB" = "This video is currently only on the web. Tap to view it in your web browser.";
/* Alert dialog content shown when user attempts to view a video that hasn't been synced */
"VIDEO_NOT_AVAILABLE"="This video is not available in offline mode.\nPlease select a video that you've downloaded onto your device.";
/* Shows in the video setting sub-table which item is selected" */
/* Shows in the video setting sub-table which item is selected */
"VIDEO_SETTING_SELECTED" = "✓ %@";
/* Button action to open app on YouTube */
"VIDEO.VIEW_ON_YOUTUBE" = "View video on YouTube";
/* Message shown for videos that can only be played external the app via YouTube. */
"VIDEO.ONLY_ON_YOUTUBE" = "This video can only be played on YouTube";
/* Button text for showing an associated item */
"VIEW" = "View";
/* Button linking to course announcements */
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions Test/VideoBlockViewControllerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// VideoBlockViewControllerTests.swift
// edX
//
// Created by Akiva Leffert on 3/15/16.
// Copyright © 2016 edX. All rights reserved.
//

import Foundation

@testable import edX

class VideoBlockViewControllerTests : SnapshotTestCase {

func testSnapshotYoutubeOnly() {
// Create a course with a youtube video
let summary = OEXVideoSummary(videoID: "some-video", name: "Youtube Video", encodings: [
"youtube": OEXVideoEncoding(name: "youtube", URL: "https://some-youtube-url", size: 12)])
let outline = CourseOutline(root: "root", blocks: [
"root" : CourseBlock(type: CourseBlockType.Course, children: ["video"], blockID: "root", name: "Root", multiDevice: true, graded: false),
"video" : CourseBlock(type: CourseBlockType.Video(summary), children: [], blockID: "video", name: "Youtube Video", multiDevice: true, graded: false)
])

let environment = TestRouterEnvironment()
environment.mockCourseDataManager.querier = CourseOutlineQuerier(courseID: "some-course", outline: outline)

let videoController = VideoBlockViewController(environment: environment, blockID: "video", courseID: "some-course")
inScreenNavigationContext(videoController) {
assertSnapshotValidWithContent(videoController.navigationController!)
}
}
}
4 changes: 4 additions & 0 deletions edX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@
77503E981B2F53C300C47229 /* StreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77503E971B2F53C300C47229 /* StreamTests.swift */; };
77509D941BE1710200B10CD3 /* TZStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77509D8F1BE1710200B10CD3 /* TZStackView.swift */; };
77509D981BE1916600B10CD3 /* UserProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77509D971BE1916600B10CD3 /* UserProfileManager.swift */; };
7753914B1C98A16600FA959C /* VideoBlockViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 775391491C98A15100FA959C /* VideoBlockViewControllerTests.swift */; };
7754200A1AA763CE006FAF5B /* NSString+OEXFormatting.m in Sources */ = {isa = PBXBuildFile; fileRef = 775420091AA763CE006FAF5B /* NSString+OEXFormatting.m */; };
775434831AD7394D00635A40 /* OEXPushNotificationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 775434821AD7394D00635A40 /* OEXPushNotificationManager.m */; };
775434861AD73D1900635A40 /* OEXParseConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 775434851AD73D1900635A40 /* OEXParseConfig.m */; };
Expand Down Expand Up @@ -894,6 +895,7 @@
77503E971B2F53C300C47229 /* StreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamTests.swift; sourceTree = "<group>"; };
77509D8F1BE1710200B10CD3 /* TZStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TZStackView.swift; sourceTree = "<group>"; };
77509D971BE1916600B10CD3 /* UserProfileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfileManager.swift; sourceTree = "<group>"; };
775391491C98A15100FA959C /* VideoBlockViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoBlockViewControllerTests.swift; sourceTree = "<group>"; };
775420081AA763CE006FAF5B /* NSString+OEXFormatting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+OEXFormatting.h"; sourceTree = "<group>"; };
775420091AA763CE006FAF5B /* NSString+OEXFormatting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+OEXFormatting.m"; sourceTree = "<group>"; };
775434801AD738AD00635A40 /* OEXPushProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OEXPushProvider.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2616,6 +2618,7 @@
BECB7B331924C0C3009C77F1 /* Tests */ = {
isa = PBXGroup;
children = (
775391491C98A15100FA959C /* VideoBlockViewControllerTests.swift */,
7745B4001C88DD0900E76ACD /* ServerChangedCheckerTests.swift */,
7765025A1C73B90C007384E7 /* OEXRearTableViewControllerTests.swift */,
7791C4EC1C651E9D0005745B /* OEXRegistrationViewControllerTests.swift */,
Expand Down Expand Up @@ -3577,6 +3580,7 @@
77BECB081B0A771700894276 /* OfflineModeViewTests.swift in Sources */,
779998231C3C1EE00058E5FE /* EnrolledCoursesViewControllerTests.swift in Sources */,
194E01941A54204B00A0CFAE /* OEXInterfaceTests.m in Sources */,
7753914B1C98A16600FA959C /* VideoBlockViewControllerTests.swift in Sources */,
E0FC64C31C85B492004E3E92 /* DiscussionDataParsingTests.swift in Sources */,
46CECC441B055B0F0073C63A /* CourseDashboardViewControllerTests.swift in Sources */,
770A27AC1A702B6500DFC6FF /* OEXDataParserTests.m in Sources */,
Expand Down

0 comments on commit b97189f

Please sign in to comment.