Skip to content

Instantly share code, notes, and snippets.

@thepearldream
Created October 1, 2016 15:40
Show Gist options
  • Save thepearldream/47c359a25466055a164729db59084609 to your computer and use it in GitHub Desktop.
Save thepearldream/47c359a25466055a164729db59084609 to your computer and use it in GitHub Desktop.
//
// ButterTorrentStreamer.m
// Butter
//
// Created by Danylo Kostyshyn on 2/23/15.
// Copyright (c) 2015 Butter Project. All rights reserved.
//
#import "ButterTorrentStreamer.h"
#import <UIKit/UIKit.h>
#import <string>
#import <libtorrent/session.hpp>
#import <libtorrent/alert.hpp>
#import <libtorrent/alert_types.hpp>
#import <MobileVLCKit/VLCMedia.h>
#import <MobileVLCKit/VLCMediaPlayer.h>
#import "CocoaSecurity.h"
using namespace libtorrent;
@interface ButterTorrentStreamer()
@property (nonatomic, strong) dispatch_queue_t alertsQueue;
@property (nonatomic, getter=isAlertsLoopActive) BOOL alertsLoopActive;
@property (nonatomic, strong) NSString *savePath;
@property (nonatomic, getter=isDownloading) BOOL downloading;
@property (nonatomic, getter=isStreaming) BOOL streaming;
@property (nonatomic) int runtime;
@property (nonatomic, copy) ButterTorrentStreamerProgress progressBlock;
@property (nonatomic, copy) ButterTorrentStreamerReadyToPlay readyToPlayBlock;
@property (nonatomic, copy) ButterTorrentStreamerFailure failureBlock;
@end
bool testing;
VLCMediaPlayer *mediaPlayer = [[VLCMediaPlayer alloc] initWithOptions:@[@"--avi-index=2", @"--play-and-pause"]];
@implementation ButterTorrentStreamer
{
session *_session;
std::vector<int> required_pieces;
}
+ (instancetype)sharedStreamer
{
static dispatch_once_t onceToken;
static ButterTorrentStreamer *sharedStreamer;
dispatch_once(&onceToken, ^{
sharedStreamer = [[ButterTorrentStreamer alloc] init];
});
return sharedStreamer;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self setupSession];
}
return self;
}
#pragma mark -
+ (NSString *)downloadsDirectory
{
NSString *downloadsDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"Downloads"];
if (![[NSFileManager defaultManager] fileExistsAtPath:downloadsDirectoryPath]) {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:downloadsDirectoryPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
NSLog(@"%@", error);
return nil;
}
}
return downloadsDirectoryPath;
}
- (void)setupSession
{
error_code ec;
_session = new session();
_session->set_alert_mask(alert::all_categories);
_session->listen_on(std::make_pair(6881, 6889), ec);
if (ec) {
NSLog(@"failed to open listen socket: %s", ec.message().c_str());
}
session_settings settings = _session->settings();
settings.announce_to_all_tiers = true;
settings.announce_to_all_trackers = true;
settings.prefer_udp_trackers = false;
settings.max_peerlist_size = 0;
_session->set_settings(settings);
}
- (void)startStreamingFromFileOrMagnetLink:(NSString *)filePathOrMagnetLink
runtime:(int)runtime
progress:(ButterTorrentStreamerProgress)progreess
readyToPlay:(ButterTorrentStreamerReadyToPlay)readyToPlay
failure:(ButterTorrentStreamerFailure)failure;
{
self.progressBlock = progreess;
self.readyToPlayBlock = readyToPlay;
self.failureBlock = failure;
self.runtime = runtime*60;
testing = NO;
self.alertsQueue = dispatch_queue_create("org.butterproject.ios.torrentstreamer.alerts", DISPATCH_QUEUE_SERIAL);
self.alertsLoopActive = YES;
dispatch_async(self.alertsQueue, ^{
[self alertsLoop];
});
error_code ec;
add_torrent_params tp;
NSString *MD5String = nil;
if ([filePathOrMagnetLink hasPrefix:@"magnet"]) {
NSString *magnetLink = filePathOrMagnetLink;
magnetLink = [magnetLink stringByAppendingString:@"&tr=udp://open.demonii.com:1337"
"&tr=udp://tracker.coppersurfer.tk:6969"];
tp.url = std::string([magnetLink UTF8String]);
MD5String = [CocoaSecurity md5:magnetLink].hexLower;
} else {
NSString *filePath = filePathOrMagnetLink;
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
MD5String = [CocoaSecurity md5WithData:fileData].hexLower;
tp.ti = new torrent_info([filePathOrMagnetLink UTF8String], ec);
if (ec) {
NSLog(@"%s", ec.message().c_str());
return;
}
} else {
NSLog(@"File doesn't exists at path: %@", filePath);
return;
}
}
NSString *halfMD5String = [MD5String substringToIndex:16];
self.savePath = [[ButterTorrentStreamer downloadsDirectory] stringByAppendingPathComponent:halfMD5String];
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:self.savePath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
NSLog(@"Can't create directory at path: %@", self.savePath);
return;
}
tp.save_path = std::string([self.savePath UTF8String]);
tp.storage_mode = storage_mode_allocate;
torrent_handle th = _session->add_torrent(tp, ec);
th.set_sequential_download(true);
if (ec) {
NSLog(@"%s", ec.message().c_str());
return;
}
self.downloading = YES;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)cancelStreaming
{
if ([self isDownloading]) {
self.alertsQueue = nil;
self.alertsLoopActive = NO;
std::vector<torrent_handle> ths = _session->get_torrents();
for(std::vector<torrent_handle>::size_type i = 0; i != ths.size(); i++) {
_session->remove_torrent(ths[i], session::delete_files);
}
required_pieces.clear();
self.progressBlock = nil;
self.readyToPlayBlock = nil;
self.failureBlock = nil;
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:self.savePath error:&error];
if (error) NSLog(@"%@", error);
self.savePath = nil;
self.streaming = NO;
self.downloading = NO;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
}
#pragma mark - Alerts Loop
#define ALERTS_LOOP_WAIT_MILLIS 5000
#define MIN_PIECES 15
#define PIECE_DEADLINE_MILLIS 100
#define LIBTORRENT_PRIORITY_SKIP 0
#define LIBTORRENT_PRIORITY_MAXIMUM 7
- (void)alertsLoop
{
std::deque<alert *> deque;
time_duration max_wait = milliseconds(ALERTS_LOOP_WAIT_MILLIS);
while ([self isAlertsLoopActive])
{
const alert *ptr = _session->wait_for_alert(max_wait);
if (ptr != nullptr) {
_session->pop_alerts(&deque);
for (std::deque<alert *>::iterator it=deque.begin(); it != deque.end(); ++it) {
std::unique_ptr<alert> alert(*it);
// NSLog(@"type:%s msg:%s", alert->what(), alert->message().c_str());
switch (alert->type()) {
case metadata_received_alert::alert_type:
case torrent_added_alert::alert_type:
NSLog(@"type:%s msg:%s", alert->what(), alert->message().c_str());
[self metadataReceivedAlert:(metadata_received_alert *)alert.get()];
break;
case block_finished_alert::alert_type:
NSLog(@"type:%s msg:%s", alert->what(), alert->message().c_str());
[self pieceFinishedAlert:(piece_finished_alert *)alert.get()];
break;
// In case the video file is already fully downloaded
case torrent_finished_alert::alert_type:
[self torrentFinishedAlert:(torrent_finished_alert *)alert.get()];
break;
default: break;
}
}
deque.clear();
}
}
}
- (void)prioritizeNextPieces:(torrent_handle)th
{
if (required_pieces.size() > 0) {
int next_required_piece = required_pieces[MIN_PIECES-1]+1;
required_pieces.clear();
boost::intrusive_ptr<const torrent_info> ti = th.torrent_file();
for (int i=next_required_piece; i<next_required_piece+MIN_PIECES; i++) {
if (i < ti->num_pieces()) {
th.piece_priority(i, LIBTORRENT_PRIORITY_MAXIMUM);
th.set_piece_deadline(i, PIECE_DEADLINE_MILLIS, torrent_handle::alert_when_available);
required_pieces.push_back(i);
}
}
}
}
- (void)processTorrent:(torrent_handle)th
{
if (![self isStreaming]) {
if (self.readyToPlayBlock) {
boost::intrusive_ptr<const torrent_info> ti = th.torrent_file();
if (ti != NULL) {
int file_index = [self indexOfLargestFileInTorrent:th];
file_entry fe = ti->file_at(file_index);
std::string path = fe.path;
NSString *fileName = [NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding];
NSURL *fileURL = [NSURL fileURLWithPath:[self.savePath stringByAppendingPathComponent:fileName]];
dispatch_async(dispatch_get_main_queue(), ^{
self.readyToPlayBlock(fileURL);
self.alertsQueue = nil;
self.alertsLoopActive = NO;
self.streaming = YES;
});
}
}
}
}
- (int)indexOfLargestFileInTorrent:(torrent_handle)th
{
boost::intrusive_ptr<const torrent_info> ti = th.torrent_file();
if (ti != NULL) {
int files_count = ti->num_files();
if (files_count > 1) {
size_type largest_size = -1;
int largest_file_index = -1;
for (int i=0; i<files_count; i++) {
file_entry fe = ti->file_at(i);
if (fe.size > largest_size) {
largest_size = fe.size;
largest_file_index = i;
}
}
return largest_file_index;
}
}
return 0;
}
#pragma mark - Logging
- (void)logPiecesStatus:(torrent_handle)th
{
NSString *pieceStatus = @"";
boost::intrusive_ptr<const torrent_info> ti = th.torrent_file();
for(std::vector<int>::size_type i=0; i!=required_pieces.size(); i++) {
int piece = required_pieces[i];
pieceStatus = [pieceStatus stringByAppendingFormat:@"%d:%d ", piece, th.have_piece(piece)];
}
// NSLog(@"%@", pieceStatus);
}
- (void)logTorrentStatus:(ButterTorrentStatus)status
{
NSString *speedString = [NSByteCountFormatter stringFromByteCount:status.downloadSpeed
countStyle:NSByteCountFormatterCountStyleBinary];
NSLog(@"%.0f%%, %.0f%%, %@/s, %d, %d",
status.bufferingProgress*100, status.totalProgreess*100,
speedString, status.seeds, status.peers);
}
#pragma mark - Alerts
- (void)metadataReceivedAlert:(metadata_received_alert *)alert
{
torrent_handle th = alert->handle;
int file_index = [self indexOfLargestFileInTorrent:th];
std::vector<int> file_priorities = th.file_priorities();
std::fill(file_priorities.begin(), file_priorities.end(), LIBTORRENT_PRIORITY_SKIP);
file_priorities[file_index] = LIBTORRENT_PRIORITY_MAXIMUM;
th.prioritize_files(file_priorities);
boost::intrusive_ptr<const torrent_info> ti = th.torrent_file();
int first_piece = ti->map_file(file_index, 0, 0).piece;
for (int i=first_piece; i<first_piece+MIN_PIECES; i++) {
required_pieces.push_back(i);
}
size_type file_size = ti->file_at(file_index).size;
int last_piece = ti->map_file(file_index, file_size, 0).piece;
required_pieces.push_back(last_piece);
for (int i=1; i<10; i++) {
required_pieces.push_back(last_piece-i);
}
for(std::vector<int>::size_type i=0; i!=required_pieces.size(); i++) {
int piece = required_pieces[i];
th.piece_priority(piece, LIBTORRENT_PRIORITY_MAXIMUM);
th.set_piece_deadline(piece, PIECE_DEADLINE_MILLIS, torrent_handle::alert_when_available);
}
}
- (void)pieceFinishedAlert:(piece_finished_alert *)alert
{
if (required_pieces.size() != 0) {
torrent_handle th = alert->handle;
torrent_status status = th.status();
int requiredPiecesDownloaded = 0;
BOOL allRequiredPiecesDownloaded = YES;
for(std::vector<int>::size_type i=0; i!=required_pieces.size(); i++) {
int piece = required_pieces[i];
if (th.have_piece(piece)) {
requiredPiecesDownloaded++;
} else {
allRequiredPiecesDownloaded = NO;
}
}
[self logPiecesStatus:th];
int requiredPieces = (int)required_pieces.size();
float bufferingProgress = 1.0 - (requiredPieces-requiredPiecesDownloaded)/(float)requiredPieces;
ButterTorrentStatus torrentStatus = {bufferingProgress,
status.progress,
status.download_rate,
status.upload_rate,
status.num_seeds,
status.num_peers};
[self logTorrentStatus:torrentStatus];
if (self.progressBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressBlock(torrentStatus);
});
}
if (allRequiredPiecesDownloaded) {
[self prioritizeNextPieces:th];
[self processTorrent:th];
}
long long requiredBytesps = 0;
if (self.runtime != 0) {
requiredBytesps = status.total_wanted/self.runtime;
}
long requiredBytes = MAX(status.total_wanted/200, 5*1024*1024);
NSLog(@"Required Bytes per Second: %lli", requiredBytesps);
if (status.download_rate > requiredBytesps && status.total_download >= requiredBytes && testing == NO && ![self isStreaming]) {
testing = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self testTorrent:th];
});
}
}
}
- (void)testTorrent:(torrent_handle)th {
boost::intrusive_ptr<const torrent_info> ti = th.torrent_file();
int file_index = [self indexOfLargestFileInTorrent:th];
file_entry fe = ti->file_at(file_index);
std::string path = fe.path;
NSString *fileName = [NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding];
NSURL *fileURL = [NSURL fileURLWithPath:[self.savePath stringByAppendingPathComponent:fileName]];
mediaPlayer.media = [VLCMedia mediaWithURL:fileURL];
[mediaPlayer play];
NSLog(@"Number of Audio Tracks: %lu", (unsigned long)[[mediaPlayer audioTrackNames] count]);
if ([[mediaPlayer audioTrackNames] count] >= 1){
if (mediaPlayer.media){
[mediaPlayer stop];
mediaPlayer.media = nil;
}
if (mediaPlayer)
mediaPlayer = nil;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self prioritizeNextPieces:th];
[self processTorrent:th];
});
} else {
NSLog(@"Not Ready... Waiting...");
if (mediaPlayer.media){
[mediaPlayer stop];
mediaPlayer.media = nil;
}
if (mediaPlayer)
mediaPlayer = nil;
testing = NO;
}
}
- (void)torrentFinishedAlert:(torrent_finished_alert *)alert
{
[self processTorrent:alert->handle];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment