iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
耳コピ用の最低限の機能は実装した。まだ、iPad対応やSongs以外の一覧対応、指定範囲の繰り返し再生等が未実装だが。
本来はオーディオセッションは、アプリケーションの単位で管理する物だと思うが、まだ、仮なので再生画面での管理とした。
- (void)viewDidLoad
{
[super viewDidLoad];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
}
- (void)viewDidUnload
{
[[AVAudioSession sharedInstance] setActive:NO error:nil];
self.dict = nil;
self.playerItem = nil;
self.player = nil;
[super viewDidUnload];
}
また、viewDidUnloadはメモリ不足時のView解放の為に呼ばれるので、位置としては適切ではないと思うが、アプリケーション単位での管理に変更する際に見直す予定だ。
再生位置をスライダで表示/変更できるようにしたのだが、再生中のスライダの更新をコールバックのハンドラで行う事にした。
- (void)viewWillAppear:(BOOL)animated
{
DBGMSG(@"%s, dict:%@", __func__, self.dict);
[super viewWillAppear:animated];
/* 選択された曲 */
NSURL *url = [self.dict objectForKey:@"URL"];
self.playerItem = [AVPlayerItem playerItemWithURL:url];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
/* 再生位置(先頭) */
self.currentTimeSlider.minimumValue = 0.0;
self.currentTimeSlider.maximumValue = CMTimeGetSeconds(self.playerItem.duration);
self.currentTimeSlider.value = 0.0;
/* 生成速度(停止) */
self.rateSlider.minimumValue = 0.0;
self.rateSlider.maximumValue = 2.0;
self.rateSlider.value = 0.0;
/* 再生位置の更新 */
const double interval = (0.5f * self.currentTimeSlider.maximumValue)
/ self.currentTimeSlider.bounds.size.width;
const CMTime time = CMTimeMakeWithSeconds(interval, NSEC_PER_SEC);
__block DetailViewController * __weak blockWeakSelf = self;
self.playerTimeObserver = [self.player addPeriodicTimeObserverForInterval:time
queue:NULL
usingBlock:^( CMTime time ) {
DetailViewController *tempSelf = blockWeakSelf;
if (! tempSelf) return;
[tempSelf _updateCurrentTimeSlider];
}];
}
また、末尾まで再生したら通知を受け取るようにした。これらについては、破棄の処理も必要だ。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_playerDidPlayToEndTime:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_playerTimeJumped:)
name:AVPlayerItemTimeJumpedNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (self.playerTimeObserver) {
[self.player removeTimeObserver:self.playerTimeObserver];
self.playerTimeObserver = nil;
}
[super viewWillDisappear:animated];
}
再生とは再生速度を1.0にする事。停止とは再生速度を0.0にする事になるため、再生速度を自由に変更できるアプリケーションでは、再生と停止のボタンは無くても構わないが、用意した。再生ボタンを押下されると、停止状態なら再生速度を1.0に設定している。
- (IBAction)play:(id)sender
{
if (0.0 == self.rateSlider.value) {
self.player.rate = 1.0;
[self _updateRateSlider];
}
}
- (IBAction)stop:(id)sender
{
self.player.rate = 0.0;
[self _updateRateSlider];
}
再生位置と再生速度のスライダで値が変更されたら、再生位置と再生速度を変更するメソッドだ。
- (IBAction)currentTimeSliderDidChanged:(id)sender
{
[self.player seekToTime:CMTimeMakeWithSeconds(self.currentTimeSlider.value, NSEC_PER_SEC)];
}
- (IBAction)rateSliderDidChanged:(id)sender
{
self.player.rate = self.rateSlider.value;
}
逆に、再生位置と再生速度をスライダに反映するメソッドだ。
- (void)_updateCurrentTimeSlider
{
const double duration = CMTimeGetSeconds( [self.player.currentItem duration] );
const double time = CMTimeGetSeconds([self.player currentTime]);
const float value = (self.currentTimeSlider.maximumValue - self.currentTimeSlider.minimumValue )
* time / duration + self.currentTimeSlider.minimumValue;
[self.currentTimeSlider setValue:value];
}
- (void)_updateRateSlider
{
self.rateSlider.value = self.player.rate;
}
最後まで再生したら、再生位置を先頭に戻している。
- (void)_playerDidPlayToEndTime:(NSNotification *)notification
{
[self.player seekToTime:CMTimeMakeWithSeconds(0.0, NSEC_PER_SEC)];
self.player.rate = 0.0;
[self currentTimeSliderDidChanged:nil];
}
これが再生画面。
iPodアプリケーションのOn-The-GoインタフェースのArtistsでは、まず、Artistsのリストが表示され、あるArtistを選択するとAlbumsリストが表示され、あるAlbumを選択するのSongsリストが表示されるが、その為で情報を取得する方法を試行錯誤してみたので、それを紹介する。
- (void)viewDidLoad
{
[super viewDidLoad];
/* Artists一覧の取得 */
MPMediaQuery *artistsQuery = [MPMediaQuery artistsQuery];
NSArray *artistsArray = [artistsQuery collections];
for (MPMediaItemCollection *mediaItemCollection in artistsArray) {
MPMediaItem *mediaItem = [mediaItemCollection representativeItem];
NSURL *artistName = (NSURL*)[mediaItem valueForProperty:MPMediaItemPropertyArtist];
NSLog(@"artist:%@", artistName);
/* Albums一覧の取得 */
MPMediaQuery *albumsQuery = [[MPMediaQuery alloc] init];
[albumsQuery addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:artistName
forProperty:MPMediaItemPropertyArtist]];
[albumsQuery setGroupingType:MPMediaGroupingAlbum];
NSArray *albums = [albumsQuery collections];
for (MPMediaItemCollection *album in albums) {
MPMediaItem *representativeItem = [album representativeItem];
NSString *albumTitle = [representativeItem valueForProperty:MPMediaItemPropertyAlbumTitle];
NSLog(@" album:%@", albumTitle);
/* Songs一覧の取得 */
NSArray *songs = [album items];
for (MPMediaItem *song in songs) {
NSString *songTitle = [song valueForProperty: MPMediaItemPropertyTitle];
NSLog(@" song:%@", songTitle);
}
}
}
}
はじめArtists一覧は木構造になっていて、Albums、Songsと辿れると予想していたのだが、そうではなくて、得られた情報から検索条件を作って取得する事になる。
今回は松戸で開催。
前回勉強会は発表されたキーが2個のキーボードのリマッパーと耳コピ用アプリケーション、設定より規約、FontPanel、デバッグTips、VMware等についての発表があった。
キーのリマッパーについては、以前、開催されたUSB分科会のように、ドライバ関連について何かやりたいという提案があった。自分も興味があるので、次回の勉強会で何か発表できたらと考えている。
Android開発環境の状況は変化してきているようで、従来はEclipseに自分でADT (Android Developer Tools)を組み込むのが主流だったが、他の開発でEclipseを使っていない人向けにADT組み込み済みのEclipseが入手できるようになった。
Get the Android SDK
これは便利になったと思っていたところに、先日のGoogle I/O 2013でIntelliJベースのAndroid Studioが発表された。
Getting Started with Android Studio
ただし、まだ、Early Access Preview版ということなので、本件ではADT組み込み済みのExlipseを利用する事にした。
以前、開発環境の雛形から、以下のような簡単なアプリケーションを作成していた。
package demo.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloWorldActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
これに、リストを表示させてみようと思う。iOSでいうところのTableViewのように。
onCreate()メソッドの内容を以下に書き換える。
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] lines = { "Line01", "Line02", "Line03", "Line04" };
ListView listView = new ListView(this);
setContentView(listView);
ArrayAdapter arrayAdapter = new ArrayAdapter(
this,
android.R.layout.simple_list_item_1,
lines);
listView.setAdapter(arrayAdapter);
}
これを実行。実機だと画面ダンプの撮り方が分からなかったので、エミュレータでの実行結果だ。
リスト表示するのはListViewクラス。これにlines変数の内容を表示させたいのだが、データとビューの中間で関連付けさせるのがArrayAdapterの役割だ。
setContentView()でlistViewを画面に配置する。iOSのサブビューの追加に似ている?
iOSのUITableViewControllerの様に、画面全体にリストを表示するアクティビティが用意されている。
前回のコードを以下の内容に変更する。
public class HelloWorldActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] lines = { "Line01", "Line02", "Line03", "Line04" };
ArrayAdapter arrayAdapter = new ArrayAdapter(
this,
android.R.layout.simple_list_item_1,
lines);
setListAdapter(arrayAdapter);
}
}
もし、アクティビティで使っているListViewが必要になれば、getListView()メソッドで取得できる。