Skip to content

Commit 6ae86a4

Browse files
Merge pull request #98 from InsectQY/add-air-play-support
feat - 添加 AirPlay 支持
2 parents dca04b1 + f72fb43 commit 6ae86a4

8 files changed

Lines changed: 214 additions & 45 deletions

File tree

Example/SJMediaCacheServer/SJViewController.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import <SJMediaCacheServer/SJMediaCacheServer.h>
1111
#import <SJVideoPlayer/SJVideoPlayer.h>
1212
#import <Masonry/Masonry.h>
13+
#import <AVKit/AVKit.h>
1314

1415
static NSString *const DEMO_URL = @"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8";
1516

@@ -34,6 +35,9 @@ - (void)viewDidLoad {
3435
}];
3536

3637
SJMediaCacheServer.shared.enabledConsoleLog = YES;
38+
39+
AVRoutePickerView *routePickerView = [[AVRoutePickerView alloc]initWithFrame:CGRectMake(40, 88, 40, 40)];
40+
[self.view addSubview:routePickerView];
3741
}
3842

3943
- (IBAction)play:(id)sender {

SJMediaCacheServer/Core/Asset/HLS/HLSAssetParser.m

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
#import "HLSAssetParser.h"
10+
#import "MCSNetworkUtils.h"
1011
#import "MCSError.h"
1112
#import "MCSURL.h"
1213
#import "NSURLRequest+MCS.h"
@@ -255,7 +256,7 @@ - (nullable NSString *)hls_findValueForAttribute:(NSString *)name startPos:(NSUI
255256
NSString *matchedName = obj.numberOfRanges == 3 ? [self substringWithRange:[obj rangeAtIndex:1]] : nil;
256257
return matchedName && [matchedName isEqualToString:name];
257258
}];
258-
259+
259260
if ( result != nil ) {
260261
NSRange valueRange = [result rangeAtIndex:2];
261262
NSUInteger valuePos = valueRange.location;
@@ -286,10 +287,19 @@ - (BOOL)hls_toBOOL {
286287

287288
- (NSString *)hls_restoreOriginalUrl:(NSURL *)resourceURL {
288289
if ( [self containsString:@"://"] ) {
290+
// Check for localhost and device IP addresses
289291
NSString *localhost = @"http://localhost";
292+
NSString *deviceIP = [MCSNetworkUtils getLocalIPAddress];
293+
290294
if ( [self hasPrefix:localhost] ) {
291295
return [NSURL URLWithString:[self substringFromIndex:localhost.length] relativeToURL:resourceURL].absoluteString;
292296
}
297+
298+
// Also check for device IP address
299+
if (deviceIP && [self hasPrefix:[NSString stringWithFormat:@"http://%@", deviceIP]]) {
300+
return [NSURL URLWithString:[self substringFromIndex:(deviceIP.length + 7)] relativeToURL:resourceURL].absoluteString;
301+
}
302+
293303
return self;
294304
}
295305
return [NSURL URLWithString:self relativeToURL:resourceURL].absoluteString;
@@ -327,12 +337,12 @@ + (nullable NSString *)proxyPlaylistWithAsset:(NSString *)assetName
327337
MCSErrorUserInfoReasonKey : @"Empty data or unsupported format!"
328338
}];
329339
}
330-
340+
331341
NSMutableString *_Nullable proxyPlaylist = nil;;
332342
if ( playlist != nil ) {
333343
NSMutableArray<__kindof HLSItem *> *parsingItems = NSMutableArray.array;
334344
proxyPlaylist = (NSMutableString *)[self parse:playlist shouldTrim:YES parsingItems:parsingItems];
335-
345+
336346
__block NSMutableArray<id<HLSVariantStream>> *_Nullable variantStreams;
337347
__block NSMutableDictionary<NSString *, HLSRenditionGroup *> *_Nullable renditionGroups;
338348
// find variant streams and renditions
@@ -363,7 +373,7 @@ + (nullable NSString *)proxyPlaylistWithAsset:(NSString *)assetName
363373
}
364374
}
365375
}];
366-
376+
367377
// selected variant stream
368378
id<HLSVariantStream> _Nullable selectedVariantStream = nil;
369379
// selected renditions
@@ -380,7 +390,7 @@ + (nullable NSString *)proxyPlaylistWithAsset:(NSString *)assetName
380390
// NSString *key = [audioGroupID stringByAppendingFormat:@"_%ld", HLSRenditionTypeAudio];
381391
// HLSRenditionGroup *group = renditionGroups[key];
382392
// NSParameterAssert(group != nil);
383-
// selectedAudioRendition =
393+
// selectedAudioRendition =
384394
// group.renditions.count > 1 ? [self selectRenditionInGroup:group originalURL:originalURL currentURL:currentURL renditionSelectionHandler:renditionSelectionHandler] :
385395
// group.renditions.firstObject;
386396
// }
@@ -403,7 +413,7 @@ + (nullable NSString *)proxyPlaylistWithAsset:(NSString *)assetName
403413
currentURL:currentURL
404414
renditionSelectionHandler:renditionSelectionHandler];
405415
}
406-
416+
407417
[parsingItems enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof HLSItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
408418
BOOL shouldRemove = NO;
409419
switch (obj.itemType) {
@@ -427,13 +437,13 @@ + (nullable NSString *)proxyPlaylistWithAsset:(NSString *)assetName
427437
break;
428438
}
429439
}
430-
440+
431441
// remove contents
432442
if ( shouldRemove ) {
433443
[proxyPlaylist replaceCharactersInRange:NSMakeRange(obj.startPosition, obj.length) withString:@""];
434444
return; // return;
435445
}
436-
446+
437447
// replace URI with proxy url
438448
NSString *URI = obj.URI;
439449
if ( URI == nil ) return;
@@ -456,7 +466,7 @@ + (NSString *)parse:(NSString *)playlist shouldTrim:(BOOL)shouldTrim parsingItem
456466
__block NSUInteger offset = 0;
457467
[lines enumerateObjectsUsingBlock:^(NSString *line, NSUInteger idx, BOOL * _Nonnull stop) {
458468
if ( shouldTrim && trimmedPlaylist.length != 0 ) [trimmedPlaylist appendString:@"\n"];
459-
469+
460470
// 去除行首尾的空白字符; 每一行要么是一个标签(以 #EXT 开头), 要么是一个 URI
461471
NSString *curLine = shouldTrim ? [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] : line;
462472
// 跳过空行和注释
@@ -465,7 +475,7 @@ + (NSString *)parse:(NSString *)playlist shouldTrim:(BOOL)shouldTrim parsingItem
465475
BOOL isUri = !isTag && !isComments && curLine.length > 0;
466476
if ( isTag ) [self handleTag:curLine offset:offset parsingItems:parsingItems context:ctx]; // 这是一个标签行
467477
else if ( isUri ) [self handleURI:curLine offset:offset context:ctx]; // 这是一个 URI 行
468-
478+
469479
if ( shouldTrim && !isComments ) [trimmedPlaylist appendString:curLine];
470480
offset = (shouldTrim ? trimmedPlaylist.length : (offset + curLine.length)) + 1; // + @"\n"
471481
}];
@@ -658,7 +668,7 @@ + (NSRange)parseByteRange:(NSString *)byteRange prevEnd:(NSUInteger)previousEnd
658668
return NSMakeRange(location, length);
659669
}
660670

661-
+ (id<HLSVariantStream>)selectVariantStreamInArray:(NSArray<id<HLSVariantStream>> *)streams
671+
+ (id<HLSVariantStream>)selectVariantStreamInArray:(NSArray<id<HLSVariantStream>> *)streams
662672
originalURL:(NSURL *)originalURL
663673
currentURL:(NSURL *)currentURL
664674
variantStreamSelectionHandler:(nullable HLSVariantStreamSelectionHandler)variantStreamSelectionHandler {
@@ -685,7 +695,7 @@ + (NSRange)parseByteRange:(NSString *)byteRange prevEnd:(NSUInteger)previousEnd
685695
if ( rendition != nil ) return rendition;
686696
}
687697
}
688-
698+
689699
return [renditions mcs_firstOrNull:^BOOL(id<HLSRendition> _Nonnull obj) {
690700
return obj.isDefault; // select default rendition
691701
}] ?: renditions.firstObject; // or first;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// MCSNetworkUtils.h
3+
// SJMediaCacheServer
4+
//
5+
// Created by changsanjiang@gmail.com on 2020/5/30.
6+
// Copyright © 2020 changsanjiang@gmail.com. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
NS_ASSUME_NONNULL_BEGIN
12+
13+
@interface MCSNetworkUtils : NSObject
14+
15+
/// Get the device's local IP address for AirPlay support
16+
/// This method returns the first available local IP address (preferably WiFi)
17+
/// @return The device's local IP address, or nil if not available
18+
+ (nullable NSString *)getLocalIPAddress;
19+
20+
/// Get the device's WiFi IP address specifically
21+
/// @return The device's WiFi IP address, or nil if WiFi is not available
22+
+ (nullable NSString *)getWiFiIPAddress;
23+
24+
/// Get all available local IP addresses
25+
/// @return Array of available local IP addresses
26+
+ (NSArray<NSString *> *)getAllLocalIPAddresses;
27+
28+
@end
29+
30+
NS_ASSUME_NONNULL_END
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//
2+
// MCSNetworkUtils.m
3+
// SJMediaCacheServer
4+
//
5+
// Created by changsanjiang@gmail.com on 2020/5/30.
6+
// Copyright © 2020 changsanjiang@gmail.com. All rights reserved.
7+
//
8+
9+
#import "MCSNetworkUtils.h"
10+
#include <ifaddrs.h>
11+
#include <arpa/inet.h>
12+
#include <net/if.h>
13+
14+
@implementation MCSNetworkUtils
15+
16+
+ (nullable NSString *)getLocalIPAddress {
17+
// First try to get WiFi IP address
18+
NSString *wifiIP = [self getWiFiIPAddress];
19+
if (wifiIP) {
20+
return wifiIP;
21+
}
22+
23+
// If WiFi is not available, get the first available local IP
24+
NSArray<NSString *> *allIPs = [self getAllLocalIPAddresses];
25+
return allIPs.firstObject;
26+
}
27+
28+
+ (nullable NSString *)getWiFiIPAddress {
29+
NSArray<NSString *> *allIPs = [self getAllLocalIPAddresses];
30+
31+
// Look for WiFi interface (en0 on iOS)
32+
for (NSString *ip in allIPs) {
33+
if ([ip hasPrefix:@"192.168."] || [ip hasPrefix:@"10."] || [ip hasPrefix:@"172."]) {
34+
return ip;
35+
}
36+
}
37+
38+
return nil;
39+
}
40+
41+
+ (NSArray<NSString *> *)getAllLocalIPAddresses {
42+
NSMutableArray<NSString *> *ipAddresses = [NSMutableArray array];
43+
44+
struct ifaddrs *interfaces = NULL;
45+
struct ifaddrs *temp_addr = NULL;
46+
47+
// Retrieve the current interfaces - returns 0 on success
48+
int success = getifaddrs(&interfaces);
49+
if (success == 0) {
50+
// Loop through linked list of interfaces
51+
temp_addr = interfaces;
52+
while (temp_addr != NULL) {
53+
if (temp_addr->ifa_addr->sa_family == AF_INET) {
54+
// Check if interface is en0 (WiFi) or en1 (Cellular)
55+
NSString *interfaceName = [NSString stringWithUTF8String:temp_addr->ifa_name];
56+
if ([interfaceName isEqualToString:@"en0"] ||
57+
[interfaceName isEqualToString:@"en1"] ||
58+
[interfaceName isEqualToString:@"en2"] ||
59+
[interfaceName isEqualToString:@"en3"]) {
60+
61+
// Get IP address
62+
char ip[INET_ADDRSTRLEN];
63+
inet_ntop(AF_INET, &(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr), ip, INET_ADDRSTRLEN);
64+
NSString *ipString = [NSString stringWithUTF8String:ip];
65+
66+
// Skip localhost
67+
if (![ipString isEqualToString:@"127.0.0.1"] && ![ipString isEqualToString:@"::1"]) {
68+
[ipAddresses addObject:ipString];
69+
}
70+
}
71+
}
72+
temp_addr = temp_addr->ifa_next;
73+
}
74+
}
75+
76+
// Free memory
77+
freeifaddrs(interfaces);
78+
79+
return [ipAddresses copy];
80+
}
81+
82+
@end

SJMediaCacheServer/Core/TcpSocketServer/MCPin.m

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#import "MCPin.h"
99
#import "MCHttpRequest.h"
1010
#import "MCSLogger.h"
11+
#import "MCSNetworkUtils.h"
1112

1213
@interface MCTimer : NSObject
1314
- (instancetype)initWithQueue:(dispatch_queue_t)queue start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(MCTimer *timer))block;
@@ -18,7 +19,7 @@ - (void)resume;
1819
- (void)suspend;
1920
- (void)invalidate;
2021
@end
21-
22+
2223
@implementation MCTimer {
2324
dispatch_semaphore_t _semaphore;
2425
dispatch_source_t _timer;
@@ -125,8 +126,15 @@ - (void)dealloc {
125126
- (void)startWithPort:(uint16_t)port {
126127
@synchronized (self) {
127128
[self _clearTimer];
128-
129-
NSURL *reqURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%hu", port]];
129+
130+
// Get device IP address for AirPlay support
131+
NSString *deviceIP = @"127.0.0.1"; // Default to localhost
132+
// For AirPlay support, we need to use the device's actual IP address
133+
NSString *localIP = [MCSNetworkUtils getLocalIPAddress];
134+
if (localIP) {
135+
deviceIP = localIP;
136+
}
137+
NSURL *reqURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%hu", deviceIP, port]];
130138
__weak typeof(self) _self = self;
131139
mTimer = [MCTimer.alloc initWithQueue:dispatch_get_global_queue(0, 0) start:mReqInterval interval:mReqInterval repeats:YES block:^(MCTimer *timer) {
132140
__strong typeof(_self) self = _self;

0 commit comments

Comments
 (0)