- 投稿日:2020-07-08T19:12:25+09:00
ExoPlayerで音声のみ簡単に再生させる方法
概要
ExoPlayerは動画・音声再生を便利にするライブラリです
動画プレイヤーを使って再生というサンプルやコード例は多いですが、ボタンを押して音声だけ再生させたいというシンプルな構造をExoPlayerで行うにはどうするかを載せます よりよい方法や間違い・指摘があれば是非ご教授ください
(MediaPlayerでも十分ですが良いライブラリはどんどん使っていきたい)導入
build.gradle// android 部分 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" }build.gradle// dependencies 部分 def exoplayer_version = "2.11.6" implementation "com.google.android.exoplayer:exoplayer:$exoplayer_version"実装
XML
ただボタンを配置するだけです
ExoPlayerのサンプルのようにPlayerViewなどは使いませんactivity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:id="@+id/audio" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="null" app:srcCompat="@drawable/ic_sound_24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>View
MainActivity.ktpackage com.example.exoplayersoundonly import android.net.Uri import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import com.google.android.exoplayer2.* import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.util.Util import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private var audioPlayer: SimpleExoPlayer? = null private val playerEventListener = object : Player.EventListener { override fun onIsPlayingChanged(isPlaying: Boolean) { } override fun onPlayerError(error: ExoPlaybackException) { } override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initPlayer(buildMediaSource()) audio.setOnClickListener { audioPlayer?.playWhenReady = true } } private fun initPlayer(mediaSource: MediaSource?) { mediaSource ?: return val trackSelector = DefaultTrackSelector(this) val loadControl = DefaultLoadControl() audioPlayer = SimpleExoPlayer.Builder(this).setTrackSelector(trackSelector).setLoadControl(loadControl).build() audioPlayer?.prepare(mediaSource) audioPlayer?.addListener(playerEventListener) audioPlayer?.playWhenReady = false } private fun buildMediaSource(): MediaSource? { return try { val uri = Uri.parse(SAMPLE_MP3) val factory = DefaultDataSourceFactory(this, Util.getUserAgent(this, getString(R.string.app_name))) ProgressiveMediaSource.Factory(factory).createMediaSource(uri) } catch (e: Exception) { null } } private fun releasePlayer() { audioPlayer?.let { it.stop() it.release() audioPlayer = null } } override fun onPause() { releasePlayer() super.onPause() } companion object { private const val SAMPLE_MP3 = "http://www.ne.jp/asahi/music/myuu/wave/menuettm.mp3" } }やっていることはとてもシンプルです
- 音声のもとになる
MediaSource
を作る- その
MediaSource
を使ってExoPlayer
インスタンスを生成する- ボタンを押した時に音声が再生されるようにする
- 使わないときは
release
するだけです
MediaSource作成
ここではMP3のURLから
MediaSource
を生成しています ただUri.parse
したものを渡すだけなのでとても簡単です
対応する形式に伴いMediaSource
を指定します 今回はmp3のためProgressiveMediaSourceになりますsnippet.ktprivate fun buildMediaSource(): MediaSource? { return try { val uri = Uri.parse(SAMPLE_MP3) val factory = DefaultDataSourceFactory(this, Util.getUserAgent(this, getString(R.string.app_name))) ProgressiveMediaSource.Factory(factory).createMediaSource(uri) } catch (e: Exception) { null } }ExoPlayerインスタンスの生成と初期化
snippet.ktprivate fun initPlayer(mediaSource: MediaSource?) { mediaSource ?: return val trackSelector = DefaultTrackSelector(this) val loadControl = DefaultLoadControl() audioPlayer = SimpleExoPlayer.Builder(this).setTrackSelector(trackSelector).setLoadControl(loadControl).build() audioPlayer?.prepare(mediaSource) audioPlayer?.addListener(playerEventListener) audioPlayer?.playWhenReady = false }Builderで生成します
その後、
#prepare
で対応するMediaSource
をセット#addListener
で状態を検知するListenerをセット(任意)playWhenReady
で再生・停止の状態を設定 (ここでは初期化のみのためfalse
を指定しています)とします
再生
onClickされた際に
playWhenReady
をtrue
にしますsnippet.ktaudio.setOnClickListener { audioPlayer?.playWhenReady = true }リリース
snippet.ktprivate fun releasePlayer() { audioPlayer?.let { it.stop() it.release() audioPlayer = null } }使わなくなったら停止・リリースしましょう
onPause
やonDestroy
でよく使われますおわりに
これで音声のみ簡単に再生させることができます
参考になれば幸いです
こちらのレポジトリから簡単にお試しできます
他初心者向けの参考
- 投稿日:2020-07-08T16:05:13+09:00
How to use wrap_content with a maximum width?
How to use wrap_content with a maximum width?
Androidのレイアウトの問題
wrap_contentのViewに最大幅を設定するにはどうしたらいいか?
↑この例で言えば、吹き出しのところの幅はwrap_contentで内容に合わせて伸縮させたいが、右側の日付の幅を引いた幅を最大値にしたい時。StackOverflow: How to use wrap_content with a maximum width?
吹き出し(TextView)、日付(TextView)をLinearLayoutに入れて、layout_weightをそれぞれ、1と0にする。
- 投稿日:2020-07-08T05:31:09+09:00
TextView上のURLリンクで任意の処理を実行する
概要
TextView 上に表示された html のリンクをクリックした際に任意の処理を実行する方法の説明です。
※コードからサクッと概要を把握したい方は こちら を見ていただければ瞬殺だと思われます。
◆ 背景
TextView では fromHtml を用いて HTML を表示することができます。
textView.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)しかし、リンク押下時に、ブラウザに表示させる以外の方法を用いたい時もあります。
◆ 振る舞いを変えたい例
TextView 上に、以下のような HTML を表示するとします。
Html 上の A タグがクリックされると、href に指定された url がブラウザ等で処理されることになります。しかし、以下のような振る舞いに変更したいことがあります。
- アプリ内の編集画面に遷移させたい。
- 利用規約ページを WebView で開きたい。
- 独自の処理を実行したいがデフォルトの動作(ブラウザを開く等)も同時に実行させたい。
要件
上述のニーズを満たすための要件を、以下のように定義しました。
- html が指定できる。
- リンクの url 文字列に対して、以下のいずれかの処理を指定できる。
- 任意の処理
- デフォルトの処理
- 任意の処理とデフォルトの処理の両方
- Kotlin の extension を用いることにより TextView に対して直接的なコーディングができる。
- Data Binding でも利用できる。
実装
◆ 呼び出し側コード例
☆ kotlin の extension 版
- 編集ページはこちら をタップすると、任意の処理が実行され、デフォルトの処理は握りつぶされる。
- 利用規約はこちら をタップすると、デフォルトの処理(ブラウザ表示や deep linking など)が実行される。
// 表示したい html 文字列 val html = """編集ページは<a href="edit_page">こちら</a>、利用規約は<a href="http://example.com">こちら</a>をご覧ください。""" // TextView に html 文字列とクリック用のハンドラを渡す textView.setLinkClickListenable(html) { url -> // この url が a タグの href に指定された文字列 when (url) { "edit_page" -> { showEditPage(); true } // showEditPage() はクリック時に実行させたい任意の処理。(falseを返せばデフォルトの処理も実行される) else -> false // false を返すとデフォルトの処理(http://example.com の表示)が実行される。(trueを返せば握りつぶせる) } }☆ Data Binding 版
Data Binding 化した以外は kotlin の extension 版と同様。
// 表示したい html 文字列 val html = """編集ページは<a href="edit_page">こちら</a>、利用規約は<a href="http://example.com">こちら</a>をご覧ください。""" // binding val binding = DataBindingUtil.setContentView<Sample4Sample4bActivityBinding>(this, R.layout.sample4_sample4b_activity) // binding に html 文字列とクリック用のハンドラを渡す binding.linkClickListenable = SimpleLinkClickListenable(html){ url -> // この url が a タグの href に指定された文字列 when (url) { "edit_page" -> { showEditPage(); true } // showEditPage() はクリック時に実行させたい任意の処理。(falseを返せばデフォルトの処理も実行される) else -> false // false を返すとデフォルトの処理(http://example.com の表示)が実行される。(trueを返せば握りつぶせる) } }layout.xml(DataBinding関連行のみを抜粋したもの)<layout> <data> <variable name="linkClickListenable" type="com.objectfanatics.infra.androidx.appcompat.widget.LinkClickListenable" /> </data> <TextView app:linkClickListenable="@{linkClickListenable}" /> </layout>◆ ライブラリ的コード
AppCompatTextViewExt.ktpackage com.objectfanatics.infra.androidx.appcompat.widget import android.text.Spannable import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.text.style.URLSpan import android.view.MotionEvent import android.view.MotionEvent.ACTION_UP import android.widget.TextView import androidx.core.text.HtmlCompat import androidx.databinding.BindingAdapter fun TextView.setLinkClickListenable(html: String, onLinkClick: (url: String) -> Boolean) { text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) movementMethod = ClickListenableLinkMovementMethod(onLinkClick) } class ClickListenableLinkMovementMethod(private val onClick: ((url: String) -> Boolean)) : LinkMovementMethod() { override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { val url = getUrl(widget, buffer, event) return when { event.action == ACTION_UP && url != null && onClick(url) -> true else -> super.onTouchEvent(widget, buffer, event) } } companion object { private fun getUrl(widget: TextView, buffer: Spannable, event: MotionEvent): String? { val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY val off = widget.layout.run { getOffsetForHorizontal(getLineForVertical(y), x.toFloat()) } return (buffer.getSpans(off, off, ClickableSpan::class.java).getOrNull(0) as? URLSpan)?.url } } } @BindingAdapter("linkClickListenable") fun TextView.setLinkClickListenable(linkClickListenable: LinkClickListenable) { linkClickListenable.run { setLinkClickListenable(html, onLinkClick) } } interface LinkClickListenable { val html: String val onLinkClick: (url: String) -> Boolean } class SimpleLinkClickListenable( override val html: String, override val onLinkClick: (url: String) -> Boolean ) : LinkClickListenableおわりに
これダメじゃんとかこういう機能も欲しいよね的なアイデアなどあればぜひ教えてくださいmm
参考