iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
保存が失敗する原因がわかった。AVMutableVideoCompositionLayerInstructionに設定するトラックが間違えていた!
これで安心して新年をむかえられる。
import UIKit
import AVFoundation
import AssetsLibrary
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBOutlet weak var rotateButton: UIButton!
@IBAction func rotate(sender:AnyObject) {
println(__FUNCTION__)
if UIImagePickerController.isSourceTypeAvailable(.SavedPhotosAlbum) == false {
return
}
let mediaUI = UIImagePickerController()
mediaUI.sourceType = .SavedPhotosAlbum
if let mediaTypes = UIImagePickerController.availableMediaTypesForSourceType(.SavedPhotosAlbum) {
mediaUI.mediaTypes = mediaTypes
NSLog("%s mediaTypes:%@", __FUNCTION__, mediaTypes)
}
// mediaUI.mediaTypes = [kUTTypeMovie]
mediaUI.mediaTypes = ["public.movie"]
mediaUI.allowsEditing = false
mediaUI.delegate = self
self.presentViewController(mediaUI, animated: true, completion: nil)
}
var mutableComposition: AVMutableComposition?
var mutableVideoComposition: AVMutableVideoComposition?
var exportSession: AVAssetExportSession?
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject: AnyObject]) {
println(__FUNCTION__)
if info[UIImagePickerControllerMediaURL] != nil {
/* アセットオブジェクトの作成 */
let url: NSURL = info[UIImagePickerControllerMediaURL] as NSURL
var options = [String: Bool]()
options[AVURLAssetPreferPreciseDurationAndTimingKey] = true
var asset = AVURLAsset(URL: url, options: options)
NSLog("%s url:%@", __FUNCTION__, url)
/* アセットから動画/音声トラックを取り出す */
let assetVideoTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] as AVAssetTrack
let assetAudioTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeAudio)[0] as AVAssetTrack
let insertionPoint: CMTime = kCMTimeZero
var error: NSError? = nil
/* コンポジションを作成 */
if mutableComposition == nil {
mutableComposition = AVMutableComposition()
/* 動画コンポジショントラックの作成 */
let compositionVideoTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
/* 動画データをコンポジションに追加 */
error = nil
compositionVideoTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetVideoTrack.timeRange.duration), ofTrack: assetVideoTrack, atTime: kCMTimeZero, error: &error)
if error != nil {
NSLog("%s insertVideoTack error:%@", __FUNCTION__, error!)
}
/* 音声コンポジショントラックの作成 */
let compositionAudioTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
/* 音声データをコンポジションに追加 */
error = nil
compositionAudioTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetAudioTrack.timeRange.duration), ofTrack: assetAudioTrack, atTime: kCMTimeZero, error: &error)
if error != nil {
NSLog("%s insertAudioTrach error:%@", __FUNCTION__, error!)
}
}
var compositionVideoTrack: AVMutableCompositionTrack? = nil
var compositionAudioTrack: AVMutableCompositionTrack? = nil
for track in mutableComposition!.tracks {
if track.isKindOfClass(AVMutableCompositionTrack) {
var mutableCompositionTrack = track as AVMutableCompositionTrack
if track.mediaType == AVMediaTypeVideo {
compositionVideoTrack = mutableCompositionTrack
}
else if track.mediaType == AVMediaTypeAudio {
compositionAudioTrack = mutableCompositionTrack
}
}
}
var instruction: AVMutableVideoCompositionInstruction
var layerInstruction: AVMutableVideoCompositionLayerInstruction
/* 移動して回転 */
let t1: CGAffineTransform = CGAffineTransformMakeTranslation(assetVideoTrack.naturalSize.height, 0.0)
let t2: CGAffineTransform = CGAffineTransformRotate(t1, ((90.0 / 180.0) * 3.14159265358979323846264338327950288))
if mutableVideoComposition == nil {
/* 動画コンポジションの作成 */
mutableVideoComposition = AVMutableVideoComposition()
mutableVideoComposition!.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height, assetVideoTrack.naturalSize.width)
mutableVideoComposition!.frameDuration = CMTimeMake(1, 30);
/* 動画コンポジション命令 */
instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition!.duration);
layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack);
layerInstruction.setTransform(t2, atTime: kCMTimeZero)
NSLog("%s instruction:%@", __FUNCTION__, instruction)
NSLog("%s layerInstruction:%@", __FUNCTION__, layerInstruction)
}
else {
mutableVideoComposition!.renderSize = CGSizeMake(mutableVideoComposition!.renderSize.height, mutableVideoComposition!.renderSize.width);
/* 動画コンポジション命令の抽出 */
instruction = mutableVideoComposition!.instructions[0] as AVMutableVideoCompositionInstruction
layerInstruction = instruction.layerInstructions[0] as AVMutableVideoCompositionLayerInstruction
/* 内容の確認 */
var existingTransform = CGAffineTransform(a: 0.0, b: 0.0, c: 0.0, d: 0.0, tx: 0.0, ty: 0.0)
if layerInstruction.getTransformRampForTime(mutableComposition!.duration, startTransform: &existingTransform, endTransform: nil, timeRange: nil) == false {
layerInstruction.setTransform(t2, atTime: kCMTimeZero)
}
else {
/* 原点補償 */
let t3: CGAffineTransform = CGAffineTransformMakeTranslation(-1.0 * assetVideoTrack.naturalSize.height / 2.0, 0.0)
let newTransform: CGAffineTransform = CGAffineTransformConcat(existingTransform, CGAffineTransformConcat(t2, t3))
layerInstruction.setTransform(newTransform, atTime: kCMTimeZero)
}
}
/* コンポジションに命令を追加 */
instruction.layerInstructions = [layerInstruction]
mutableVideoComposition!.instructions = [instruction]
/* 出力URL */
var documentsPath: NSString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
error = nil
NSFileManager.defaultManager().createDirectoryAtPath(documentsPath, withIntermediateDirectories: true, attributes: nil, error: &error)
if error != nil {
NSLog("%s createDir error:%@", __FUNCTION__, error!)
}
let now = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.locale = NSLocale(localeIdentifier: "ja_JP")
dateFormatter.dateFormat = "yyyyMMddHHmmss"
let filename = String(format: "%@.mp4", arguments: [dateFormatter.stringFromDate(now)])
var exportPath: NSString = documentsPath.stringByAppendingPathComponent(filename)
error = nil
//NSFileManager.defaultManager().removeItemAtPath(exportPath, error: &error)
if error != nil {
NSLog("%s removeFile error:%@", __FUNCTION__, error!)
}
var exportUrl: NSURL = NSURL.fileURLWithPath(exportPath)!
/* セッションを作成し、フォトライブラリに書き出す */
exportSession = AVAssetExportSession(asset: mutableComposition!.copy() as AVAsset, presetName: AVAssetExportPresetHighestQuality)
exportSession!.videoComposition = mutableVideoComposition
exportSession!.outputURL = exportUrl
exportSession!.outputFileType = AVFileTypeQuickTimeMovie
exportSession!.exportAsynchronouslyWithCompletionHandler({
() -> Void in
NSLog("%@", __FUNCTION__)
switch self.exportSession!.status {
case AVAssetExportSessionStatus.Completed:
NSLog("%@ AVAssetExportSessionStatus.Completed", __FUNCTION__)
let assetsLib = ALAssetsLibrary()
assetsLib.writeVideoAtPathToSavedPhotosAlbum(exportUrl, completionBlock: {
(nsurl, error) -> Void in
if error != nil {
NSLog("%@ error:%@", __FUNCTION__, error)
}
})
case AVAssetExportSessionStatus.Failed:
NSLog("%@ AVAssetExportSessionStatus.Failed exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
case AVAssetExportSessionStatus.Cancelled:
NSLog("%@ AVAssetExportSessionStatus.Cancelled exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
default:
NSLog("%@ none exporter:%@", __FUNCTION__, self.exportSession!)
}
})
}
picker.dismissViewControllerAnimated(true, completion: nil);
}
}
AsyncTaskの仕様をよく読むと気になることが説明されている。UIスレッドで使用するという箇所だ。
Threading rulesでスレッド関連の説明がされているが、クラスのロードはUIスレッド上で。ただし、JELLY_BEANからは自動で対応している。インスタンスはUIスレッドで生成すること。
ソースを確認してみよう。Android SDKが置かれているディレクトリ配下で以下のコマンドを実行。
$ cd /Applications/Development/android-sdk-macosx
$ find . -name AsyncTask.java -print
./samples/android-19/ui/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./samples/android-21/ui/DisplayingBitmaps/Application/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./sources/android-19/android/os/AsyncTask.java
./sources/android-21/android/os/AsyncTask.java
Android 5.0 Lollipopのソースを確認してみよう。
public abstract class AsyncTask<Params, Progress, Result> {
:
private static final InternalHandler sHandler = new InternalHandler();
:
/** @hide Used to force static handler to be created. */
public static void init() {
sHandler.getLooper();
}
:
}
AsyncTaskのHandlerはクラス変数になっている。そして、HandlerのLooperはクラスメソッドで設定されている。つまり、最初にAsyncTaskのインスタンスを生成する際にHandlerとLooperが保持されることになる。
なぜ、HandlerとLooperが必要か?それは、onCancelledなど、UIスレッドで呼び出すことになっているメソッドがUIスレッドで呼ばれるようにする為だろう。こういうわけだ、AsyncTaskはUIスレッドで生成されることになるので、最初の生成時にUIスレッドのLooperを保持しておいて、別スレッドで処理を終えた後に、このLooperを使って、UIスレッドで結果を返す。
なので、最初のAsyncTaskの生成をUIスレッド以外で行うと、そのスレッドにLooperがない場合は、myLooperがnullの例外が発生することになる。Looperが存在しても、onCencelledなどがUIスレッドとは異なるスレッドで呼ば出されることになってしまう。
気をつけよう。著者はこれでハマった。
UDP通信のサンプルを作成したので、ざっと説明する。
ADCのサンプルコードUDPEchoを改造して作成した。
UDP通信する部分はクラスにしている。
@protocol UDPDelegate;
@interface UDP : NSObject
@property (nonatomic, weak, readwrite) id<UDPDelegate> delegate;
@property (nonatomic, assign, readonly, getter=isServer) BOOL server;
@property (nonatomic, copy, readonly ) NSString * hostName;
@property (nonatomic, copy, readonly ) NSData * hostAddress;
@property (nonatomic, assign, readonly ) NSUInteger port;
- (void)startServerOnPort:(NSUInteger)port;
- (void)startConnectedToHostName:(NSString *)hostName port:(NSUInteger)port;
- (void)sendData:(NSData *)data;
- (void)stop;
@end
@protocol UDPDelegate <NSObject>
@optional
- (void)udp:(UDP *)udp didReceiveData:(NSData *)data fromAddress:(NSData *)addr;
- (void)udp:(UDP *)udp didReceiveError:(NSError *)error;
- (void)udp:(UDP *)udp didSendData:(NSData *)data toAddress:(NSData *)addr;
- (void)udp:(UDP *)udp didFailToSendData:(NSData *)data toAddress:(NSData *)addr error:(NSError *)error;
- (void)udp:(UDP *)udp didStartWithAddress:(NSData *)address;
- (void)udp:(UDP *)udp didStopWithError:(NSError *)error;
@end
#import <sys/socket.h>
#import <netinet/in.h>
#import <fcntl.h>
#import <unistd.h>
#import <Foundation/Foundation.h>
#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR
#import <CFNetwork/CFNetwork.h>
#else
#import <oreServices/CoreServices.h>
#endif
#import "UDP.h"
@interface UDP ()
@property (nonatomic, copy, readwrite) NSString * hostName;
@property (nonatomic, copy, readwrite) NSData * hostAddress;
@property (nonatomic, assign, readwrite) NSUInteger port;
- (void)stopHostResolution;
- (void)stopWithError:(NSError *)error;
- (void)stopWithStreamError:(CFStreamError)streamError;
@end
@implementation UDP {
CFHostRef _cfHost;
CFSocketRef _cfSocket;
}
- (id)init
{
NSLog(@"%s", __func__);
self = [super init];
if (self != nil) {
}
return self;
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
- (BOOL)isServer
{
NSLog(@"%s", __func__);
return self.hostName == nil;
}
- (void)sendData:(NSData *)data toAddress:(NSData *)addr
{
NSLog(@"%s", __func__);
int err;
int sock;
ssize_t bytesWritten;
const struct sockaddr * addrPtr;
socklen_t addrLen;
sock = CFSocketGetNative(self->_cfSocket);
if (addr == nil) {
addr = self.hostAddress;
addrPtr = NULL;
addrLen = 0;
} else {
addrPtr = [addr bytes];
addrLen = (socklen_t) [addr length];
}
bytesWritten = sendto(sock, [data bytes], [data length], 0, addrPtr, addrLen);
if (bytesWritten < 0) {
err = errno;
} else if (bytesWritten == 0) {
err = EPIPE;
} else {
err = 0;
}
if (err == 0) {
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didSendData:toAddress:)] ) {
[self.delegate udp:self didSendData:data toAddress:addr];
}
} else {
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didFailToSendData:toAddress:error:)] ) {
[self.delegate udp:self
didFailToSendData:data
toAddress:addr
error:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
}
}
}
- (void)readData
{
NSLog(@"%s", __func__);
int err;
int sock;
struct sockaddr_storage addr;
socklen_t addrLen;
uint8_t buffer[65536];
ssize_t bytesRead;
sock = CFSocketGetNative(self->_cfSocket);
addrLen = sizeof(addr);
bytesRead = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &addrLen);
if (bytesRead < 0) {
err = errno;
} else if (bytesRead == 0) {
err = EPIPE;
} else {
NSData *dataObj;
NSData *addrObj;
err = 0;
dataObj = [NSData dataWithBytes:buffer length:(NSUInteger) bytesRead];
addrObj = [NSData dataWithBytes:&addr length:addrLen ];
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didReceiveData:fromAddress:)] ) {
[self.delegate udp:self didReceiveData:dataObj fromAddress:addrObj];
}
if (self.isServer) {
[self sendData:dataObj toAddress:addrObj];
}
}
if (err != 0) {
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didReceiveError:)] ) {
[self.delegate udp:self
didReceiveError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
}
}
}
static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
NSLog(@"%s", __func__);
UDP *udp = (__bridge UDP *)info;
[udp readData];
}
- (BOOL)setupSocketConnectedToAddress:(NSData *)address port:(NSUInteger)port error:(NSError **)errorPtr
{
NSLog(@"%s", __func__);
int err;
int junk;
int sock;
const CFSocketContext context = { 0, (__bridge void *)(self), NULL, NULL, NULL };
CFRunLoopSourceRef rls;
err = 0;
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
err = errno;
}
if (err == 0) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
if (address == nil) {
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
err = bind(sock, (const struct sockaddr *) &addr, sizeof(addr));
} else {
if ([address length] > sizeof(addr)) {
[address getBytes:&addr length:sizeof(addr)];
} else {
[address getBytes:&addr length:[address length]];
}
addr.sin_port = htons(port);
err = connect(sock, (const struct sockaddr *) &addr, sizeof(addr));
}
if (err < 0) {
err = errno;
}
}
if (err == 0) {
int flags;
flags = fcntl(sock, F_GETFL);
err = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
if (err < 0) {
err = errno;
}
}
if (err == 0) {
self->_cfSocket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, SocketReadCallback, &context);
sock = -1;
rls = CFSocketCreateRunLoopSource(NULL, self->_cfSocket, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
}
if (sock != -1) {
junk = close(sock);
}
if ( (self->_cfSocket == NULL) && (errorPtr != NULL) ) {
*errorPtr = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil];
}
return (err == 0);
}
- (void)startServerOnPort:(NSUInteger)port
{
NSLog(@"%s", __func__);
if (self.port == 0) {
BOOL success;
NSError * error;
success = [self setupSocketConnectedToAddress:nil port:port error:&error];
if (success) {
self.port = port;
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStartWithAddress:)] ) {
CFDataRef localAddress;
localAddress = CFSocketCopyAddress(self->_cfSocket);
[self.delegate udp:self didStartWithAddress:(__bridge NSData *) localAddress];
CFRelease(localAddress);
}
} else {
[self stopWithError:error];
}
}
}
- (void)hostResolutionDone
{
NSLog(@"%s", __func__);
NSError * error;
Boolean resolved;
NSArray * resolvedAddresses;
error = nil;
resolvedAddresses = (__bridge NSArray *) CFHostGetAddressing(self->_cfHost, &resolved);
if ( resolved && (resolvedAddresses != nil) ) {
for (NSData * address in resolvedAddresses) {
BOOL success;
const struct sockaddr * addrPtr;
NSUInteger addrLen;
addrPtr = (const struct sockaddr *) [address bytes];
addrLen = [address length];
success = NO;
if (
(addrPtr->sa_family == AF_INET)
) {
success = [self setupSocketConnectedToAddress:address port:self.port error:&error];
if (success) {
CFDataRef hostAddress;
hostAddress = CFSocketCopyPeerAddress(self->_cfSocket);
self.hostAddress = (__bridge NSData *) hostAddress;
CFRelease(hostAddress);
}
}
if (success) {
break;
}
}
}
if ( (self.hostAddress == nil) && (error == nil) ) {
error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil];
}
if (error == nil) {
[self stopHostResolution];
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStartWithAddress:)] ) {
[self.delegate udp:self didStartWithAddress:self.hostAddress];
}
} else {
[self stopWithError:error];
}
}
static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info)
{
NSLog(@"%s", __func__);
UDP *udp;
udp = (__bridge UDP *)info;
if ( (error != NULL) && (error->domain != 0) ) {
[udp stopWithStreamError:*error];
} else {
[udp hostResolutionDone];
}
}
- (void)startConnectedToHostName:(NSString *)hostName port:(NSUInteger)port
{
NSLog(@"%s", __func__);
if (self.port == 0) {
Boolean success;
CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFStreamError streamError;
self->_cfHost = CFHostCreateWithName(NULL, (__bridge CFStringRef) hostName);
CFHostSetClient(self->_cfHost, HostResolveCallback, &context);
CFHostScheduleWithRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
success = CFHostStartInfoResolution(self->_cfHost, kCFHostAddresses, &streamError);
if (success) {
self.hostName = hostName;
self.port = port;
} else {
[self stopWithStreamError:streamError];
}
}
}
- (void)sendData:(NSData *)data
{
NSLog(@"%s", __func__);
if (self.isServer || (self.hostAddress == nil) ) {
} else {
[self sendData:data toAddress:nil];
}
}
- (void)stopHostResolution
{
NSLog(@"%s", __func__);
if (self->_cfHost != NULL) {
CFHostSetClient(self->_cfHost, NULL, NULL);
CFHostCancelInfoResolution(self->_cfHost, kCFHostAddresses);
CFHostUnscheduleFromRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRelease(self->_cfHost);
self->_cfHost = NULL;
}
}
- (void)stop
{
NSLog(@"%s", __func__);
self.hostName = nil;
self.hostAddress = nil;
self.port = 0;
[self stopHostResolution];
if (self->_cfSocket != NULL) {
CFSocketInvalidate(self->_cfSocket);
CFRelease(self->_cfSocket);
self->_cfSocket = NULL;
}
}
- (void)noop
{
NSLog(@"%s", __func__);
}
- (void)stopWithError:(NSError *)error
{
NSLog(@"%s", __func__);
[self stop];
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStopWithError:)] ) {
[self performSelector:@selector(noop) withObject:nil afterDelay:0.0];
[self.delegate udp:self didStopWithError:error];
}
}
- (void)stopWithStreamError:(CFStreamError)streamError
{
NSLog(@"%s", __func__);
NSDictionary * userInfo;
NSError * error;
if (streamError.domain == kCFStreamErrorDomainNetDB) {
userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInteger:streamError.error], kCFGetAddrInfoFailureKey,
nil
];
} else {
userInfo = nil;
}
error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];
[self stopWithError:error];
}
@end
そして、ViewControllerから呼び出す。
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *inputTextField;
@property (weak, nonatomic) IBOutlet UIButton *sendButton;
@property (weak, nonatomic) IBOutlet UILabel *outputLabel;
- (IBAction)send:(id)sender;
@end
#import "ViewController.h"
#import "UDP.h"
@interface ViewController () <UDPDelegate>
@property UDP *server;
@property UDP *client;
- (void)runServerOnPort:(NSUInteger)port;
- (void)runClientWithHost:(NSString *)host port:(NSUInteger)port;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self runServerOnPort:3054];
[self runClientWithHost:@"localhost" port:3054];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (IBAction)send:(id)sender
{
NSLog(@"%s", __func__);
NSData *data = [[NSString stringWithString:self.inputTextField.text] dataUsingEncoding:NSUTF8StringEncoding];
[self.client sendData:data];
}
- (void)runServerOnPort:(NSUInteger)port
{
NSLog(@"%s", __func__);
self.server = [[UDP alloc] init];
self.server.delegate = self;
[self.server startServerOnPort:port];
}
- (void)runClientWithHost:(NSString *)host port:(NSUInteger)port
{
NSLog(@"%s", __func__);
self.client = [[UDP alloc] init];
self.client.delegate = self;
[self.client startConnectedToHostName:host port:port];
}
- (void)udp:(UDP *)udp didReceiveData:(NSData *)data fromAddress:(NSData *)addr
{
NSLog(@"%s data(%@)", __func__, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
self.outputLabel.text = msg;
}
- (void)udp:(UDP *)udp didReceiveError:(NSError *)error
{
NSLog(@"%s", __func__);
self.outputLabel.text = [error description];
}
- (void)udp:(UDP *)udp didSendData:(NSData *)data toAddress:(NSData *)addr
{
NSLog(@"%s data(%@)", __func__, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
- (void)udp:(UDP *)udp didFailToSendData:(NSData *)data toAddress:(NSData *)addr error:(NSError *)error
{
NSLog(@"%s", __func__);
NSLog(@"failed with error: %@", [error description]);
}
- (void)udp:(UDP *)udp didStartWithAddress:(NSData *)address
{
NSLog(@"%s", __func__);
}
- (void)udp:(UDP *)udp didStopWithError:(NSError *)error
{
NSLog(@"%s", __func__);
NSLog(@"failed with error: %@", [error description]);
}
@end