江戸一番のジャスタウェイ職人のブログ

江戸一番のジャスタウェイ職人

動画再生の為に止まったミュージックプレイヤーを動画終了後に戻す

私はいつもイヤホンで音楽を聞きながらスマートフォンを操作するのですが、最近は動画によってそれが止まることが増えました、例えば FacebookTwitter、ニュースアプリ...etc

数秒の動画や動画広告の為に何度もミュージックプレイヤーを再生し直すのは鬱陶しくなんとかならないかと思っていましたが、ちゃんと動画終わった後にミュージックプレイヤーが鳴り始めるアプリがあって、「なんだできんじゃん!!!じゃあJustawayも対応するか」とコードを書き始めた次第です。

ざっとこういったコードで対応しています。

Android

final boolean musicWasPlaying = ((AudioManager) getSystemService(Context.AUDIO_SERVICE)).isMusicActive();

// ...

if (musicWasPlaying) {
	Intent i = new Intent("com.android.music.musicservicecommand");
	i.putExtra("command", "play");
	sendBroadcast(i);
}

Justaway - Google Play の Android アプリ

iOS

do {
    try AVAudioSession.sharedInstance().setActive(false, withOptions:
        AVAudioSessionSetActiveOptions.NotifyOthersOnDeactivation)
} catch {
    print("AVAudioSession setActive failure.")
}

aska.hatenablog.com

半年ぶりにJustaway for Androidを更新しました

あけましておめでとうございます。

遂に動画の再生が可能になりました、実に半年ぶりの更新です。

Justaway - Google Play の Android アプリ

「動くやろ〜」と思ってrelease apkの動作確認しないでアップデートしたら起動しなくなるなど不具合が起きるなどトラブルもありました。先人のおかげですぐリカバリーできました、本当にすみませんでした。

Android - GsonとProguard - Qiita

ProGuardは使うべきか

オープンソースなので難読化する意味は無いのですが、minifyによるパフォーマンスの恩恵が少しでもあるなら使おうというスタンスです。

半年間何をしていたのか

for iOS の開発と仕事とプライベートで時間がなかったことと、iOS開発中はiPhoneを持ち歩いていたため、そっちに動画再生機能をつけて楽しんでいました、本当にすみませんでした。

ぶっちゃけどうなんだ

for iOS がリリースされたら作り直すつもりで居たんですが、Twitter社からは書き込み権限を止められAppleからは利用規約を作れと迫られ進んでいません。

流石に動画の再生機能が無いまま半年放置はひどかったなと思っています。

Googleからマテリアルデザインの公開以降、Android向け非公式クライアントもスタイリッシュなのが増えてきましたね。今更更新を続ける意義があるのか疑問をお持ちの方もいらっしゃるでしょう、自分がいちばん使うアプリは自分で作っていたいという想いと、なぜかいまでもJustawayがいいという声がまだあり、続けようかと思っています。

今後の予定

投票APIが来たら対応を、それまではコードもデザインも周回遅れなのを取り戻す予定です、作り直す方が早いかとも思ったんですが結局2つのプロジェクトのソースを見比べながら移植する事になりまたそれも苦行なので、本職らしくリファクタリングしようかと思います。

iPhoneユーザーはバックキーじゃなくてジェスチャーを使うんだよ!!!

iPhoneの画面下にバックキーを追加する強化液晶ガラス保護シートが話題になったりホームボタンのダブルタップで押し下がる画面を見て私も「だからバックキーいるじゃん!!!」と思っていた。

しかしずっとAndroidを使っているうちにiPhoneの操作でジェスチャーをよく使うというのをすっかり忘れていただけだったのだ。

iPhone 6sをしばらく使っていると殆どシチュエーション、例えばプッシュで追加された画面は右スワイプ、画像プレビューなどは上下スワイプでバック相当の動きが出来ていて、画面の左上をタップすることなんて殆ど無い。

Androidはバックキーがあるのでとにかく前の状態に戻りたい時は考えずにバックキーを押すしこれが便利でスマホには必須だと思っていたけどジェスチャーというもの、ジェスチャーというものを、彼の事を忘れていた。

そういった具合にiPhoneアプリはジェスチャーをバリバリ使っていくのがスタンダードで画面下に戻るボタンを置くのは悪手だなと思いJustawayを改良しています。

Justaway for iOS βテスト申し込み

UDIDによるテスター登録上限の100デバイスにそろそろ到達してしまう為、テスター登録をTestFlight(上限1000人)に切り替えます。

TestFlight によるテストには事前のメールアドレス登録が必要です、こちらにメールアドレスを記入下さい。

Justaway for iOS βテスト申し込み

当面新しいバージョンはdeploygate、TestFlight両方で配布するので既にdeploygateに登録している方は新たに登録頂かなくてOKです。

β版の配布は週内には開始予定です。

正式版のリリースはAppleの審査通過次第と考えていますが、はじめての審査なので月末まで掛かる見込みです。

iOS標準Frameworkで作れるオーバーレイタイプの動画プレイヤーを付けました

AndroidVine Player が好きであのさり気ない感じの動画プレイヤーを Justaway for iOS に組み込みました

Vine Playerの公開とVineの動画をAndroidで再生するお話 - おぼえにくいおぼえがき

サムネイルをタップするとオーバーレイで動画再生ビューが開いてタップすると閉じるというシンプルな仕様です


ソース(新)

2015/9/22 MPMoviePlayerController が非推奨になっていたので更新しました。

参考

//-- SomeViewController.swift

import AVFoundation

    let playerView = AVPlayerView()

    func configureView() {

        // タップされたら動画終了
        playerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "hideVideo"))

        // 背景を半透明に
        playerView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
    }
    
    func configureEvent() {

        // 動画の最後に来たら最初から再生(リピート)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "endVideo", name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)
    }

    func showVideo(videoURL: NSURL) {
        guard let view = UIApplication.sharedApplication().keyWindow else {
            return
        }

        // 画面いっぱい
        playerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)

        // このURLの動画プレイヤーを
        playerView.player = AVPlayer(URL: videoURL)
        playerView.player?.actionAtItemEnd = AVPlayerActionAtItemEnd.None

        // アスペクト比を維持しつつ
        playerView.setVideoFillMode(AVLayerVideoGravityResizeAspect)
        view.addSubview(playerView)

        // 再生していく人生
        playerView.player?.play()
    }
    
    // 動画の時代終わり
    func hideVideo() {
        playerView.player?.pause()
        playerView.removeFromSuperview()
    }
    
    // リピート
    func endVideo() {
        playerView.player?.currentItem?.seekToTime(kCMTimeZero)
    }

//-- AVPlayerView.swift

import UIKit
import AVFoundation

class AVPlayerView: UIView {
    
    var player: AVPlayer? {
        get {
            let layer: AVPlayerLayer = self.layer as! AVPlayerLayer
            return layer.player
        }
        set(newValue) {
            let layer: AVPlayerLayer = self.layer as! AVPlayerLayer
            layer.player = newValue
        }
    }
    
    override class func layerClass() -> AnyClass {
        return AVPlayerLayer.self
    }
    
    func setVideoFillMode(mode: String) {
        let layer: AVPlayerLayer = self.layer as! AVPlayerLayer
        layer.videoGravity = mode
    }
}

ソース

import MediaPlayer

    var moviePlayer: MPMoviePlayerController?

    func showVideo(videoURL: NSURL) {
        guard let view = UIApplication.sharedApplication().keyWindow else {
            return
        }
        
        let moviePlayer = MPMoviePlayerController(contentURL: videoURL)
        
        // view playerスタイル(再生コントロールがない)
        moviePlayer.controlStyle = MPMovieControlStyle.None

        // ショートムービーはリピートした方がいい
        moviePlayer.repeatMode = MPMovieRepeatMode.One

        // 画面いっぱい
        moviePlayer.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)

        // 背景透過
        moviePlayer.backgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)

        // 再帰的にclearColor指定しないと真っ黒
        moviePlayer.view.backgroundColor = UIColor.clearColor()
        for subView in moviePlayer.view.subviews {
            subView.backgroundColor = UIColor.clearColor()
        }

        // タップして閉じるをやっていく
        let screenView = UIView(frame: view.bounds)
        screenView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "hideVideo"))
        moviePlayer.view.addSubview(screenView)

        // 最前面にaddSubview
        view.addSubview(moviePlayer.view)

        // 自動再生
        moviePlayer.play()

        // インスタンスを保持しておかないと再生できない(viewだけじゃなダメ)
        self.moviePlayer = moviePlayer
    }
    
    func hideVideo() {
        // 停止してから消さないと音が止まらない
        self.moviePlayer?.stop()
        self.moviePlayer?.view.removeFromSuperview()
    }

SwiftでTwitter Streaming APIをparseするのに便利なNSData分割読み込みライブラリ

Twitter Streaming APIはCRLF区切りでJSONを送ってくるのですが、これを都度Stringに変換しcomponentsSeparatedByStringで配列にし、最後の手前まではNSDataに戻してJSONに、最後の要素はバッファに詰めて次回処理で先頭にくっつける...というのがいかにもパフォーマンス上よろしくなさそうなので、NSDataからデータを変換せずにある区切り毎にデータを読めるライブラリを書きました。

以前のコードと比較すると7倍弱高速化されました、パフォーマンスを体感することはないでしょうが消費電力が下がるという見方もできます。

また、コードの可読性も高まりました。

import Foundation
import MutableDataScanner
import SwiftyJSON

class TwitterAPIStreamingRequest: NSObject, NSURLSessionDataDelegate {

    let scanner = MutableDataScanner(delimiter: "\r\n")

    // ...

    func connection(connection: NSURLConnection, didReceiveData data: NSData) {
        self.scanner.appendData(data)
        while let data = self.scanner.nextLine() {
            if data.length > 0 {
                let json = JSON(data: data)
            }
        }
    }
}

github.com
Swift 2以降のみ対応です