This sample code shows how to integrate PallyCon Multi-DRM with Better Player project. It supports streaming playback of DRM-protected contents as below.
- Android: MPEG-DASH content protected by Widevine DRM
- iOS: HLS content protected by FairPlay Streaming DRM
If you want download/offline scenario support, you may need to implement the feature on your own or use our
Multi-DRM Client SDK for Flutter
product which is under development.
This sample was tested with the below components and environments.
- Flutter 3.0 or later
- Better Player 0.0.82 or later
In terms of Widevine DRM support for Android app, PallyCon Multi-DRM is compatible with Better Player without any modification. You can simply replace the below values in Better Player example as described in Better Player's DRM configuration guide.
- Widevine content URL : Input your DASH mpd URL in
url
parameter - License server URL : Input our DRM license server URL (
https://license-global.pallycon.com/ri/licenseManager.do
) - DRM custom data : Input PallyCon DRM license token string as
pallycon-customdata-v2
custom header.
Please refer to
drm_page.dart
source code in this sample for more details.
Unlike the Widevine integration for Android, FPS integration for iOS requires modification of Better Player code and additional implementation to support PallyCon Multi-DRM.
You may follow the below steps or refer to the source files in ios > Classes
folder of this sample project.
-
Modify the line no. 309 to get drmHeaders value
NSDictionary* headers = dataSource[@"headers"]; **NSDictionary* drmHeaders = dataSource[@"drmHeaders"];** NSString* cacheKey = dataSource[@"cacheKey"];
-
Modify the line no. 341 to pass the drmHeaders value to
BetterPlayer.h
( also need to modifyBetterPlayer.h
to addwidthDrmheaders
parameter )if (drmHeaders != [NSNull null] || drmHeaders != NULL) { [player setDataSourceURL:[NSURL URLWithString:uriArg] withKey:key withCertificateUrl:certificateUrl withLicenseUrl: licenseUrl withHeaders:headers withDrmHeaders:drmHeaders withCache: useCache cacheKey:cacheKey cacheManager:_cacheManager overriddenDuration:overriddenDuration videoExtension: videoExtension]; } else { [player setDataSourceURL:[NSURL URLWithString:uriArg] withKey:key withCertificateUrl:certificateUrl withLicenseUrl: licenseUrl withHeaders:headers withCache: useCache cacheKey:cacheKey cacheManager:_cacheManager overriddenDuration:overriddenDuration videoExtension: videoExtension]; }
The original Better Player project has BetterPlayerEzDrmAssetsLoaderDelegate
which is for EZDRM integration. You need to add BetterPlayerPallyconDrmAssetsLoaderDelegate
to support PallyCon Multi-DRM integration.
Please refer to the source files under ios > Classes
folder of this sample.
You need to modify BetterPlayer.h
and BetterPlayer.m
source codes to receive the drmHeaders
value from BetterPlayerPallyconDrmAssetsLoaderDelegate
.
-
BetterPlayer.h
(overloading setDataSourceURL)@property(readonly, nonatomic) BetterPlayerPallyconDrmAssetsLoaderDelegate* pallyconLoaderDelegate; - (void)setDataSourceURL:(NSURL*)url withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl withHeaders:(NSDictionary*)headers withDrmHeaders:(NSDictionary*)drmHeaders withCache:(BOOL)useCache cacheKey:(NSString*)cacheKey cacheManager:(CacheManager*)cacheManager overriddenDuration:(int) overriddenDuration videoExtension: (NSString*) videoExtension;
-
BetterPlayer.m
- (void)setDataSourceURL:(NSURL*)url withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl withHeaders:(NSDictionary*)headers withDrmHeaders:(NSDictionary*)drmHeaders withCache:(BOOL)useCache cacheKey:(NSString*)cacheKey cacheManager:(CacheManager*)cacheManager overriddenDuration:(int) overriddenDuration videoExtension: (NSString*) videoExtension { _overriddenDuration = 0; if (headers == [NSNull null] || headers == NULL){ NSLog(@"header is null"); headers = @{}; } if (drmHeaders == [NSNull null] || drmHeaders == NULL){ NSLog(@"drmHeader is null"); drmHeaders = @{}; } for (id key in drmHeaders) { NSLog(@"key: %@, value: %@ \n", key, [drmHeaders objectForKey:key]); } AVPlayerItem* item; if (useCache){ if (cacheKey == [NSNull null]){ cacheKey = nil; } if (videoExtension == [NSNull null]){ videoExtension = nil; } item = [cacheManager getCachingPlayerItemForNormalPlayback:url cacheKey:cacheKey videoExtension: videoExtension headers:headers]; } else { AVURLAsset* asset = [AVURLAsset URLAssetWithURL:url options:@{@"AVURLAssetHTTPHeaderFieldsKey" : headers}]; if (certificateUrl && certificateUrl != [NSNull null] && [certificateUrl length] > 0) { NSURL * certificateNSURL = [[NSURL alloc] initWithString: certificateUrl]; NSURL * licenseNSURL = [[NSURL alloc] initWithString: licenseUrl]; _pallyconLoaderDelegate = [[BetterPlayerPallyconDrmAssetsLoaderDelegate alloc] init:certificateNSURL withLicenseURL:licenseNSURL withHeaders:drmHeaders]; dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1); dispatch_queue_t streamQueue = dispatch_queue_create("streamQueue", qos); [asset.resourceLoader setDelegate:_pallyconLoaderDelegate queue:streamQueue]; } item = [AVPlayerItem playerItemWithAsset:asset]; } if (@available(iOS 10.0, *) && overriddenDuration > 0) { _overriddenDuration = overriddenDuration; } return [self setDataSourcePlayerItem:item withKey:key]; }