20200708のAndroidに関する記事は3件です。

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.kt
package 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"
    }
}

やっていることはとてもシンプルです

  1. 音声のもとになるMediaSourceを作る
  2. そのMediaSourceを使ってExoPlayerインスタンスを生成する
  3. ボタンを押した時に音声が再生されるようにする
  4. 使わないときはreleaseする

だけです

MediaSource作成

ここではMP3のURLからMediaSourceを生成しています ただUri.parseしたものを渡すだけなのでとても簡単です
対応する形式に伴いMediaSourceを指定します 今回はmp3のためProgressiveMediaSourceになります

snippet.kt
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
    }
}

ExoPlayerインスタンスの生成と初期化

snippet.kt
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
}

Builderで生成します
その後、

  • #prepareで対応するMediaSourceをセット
  • #addListenerで状態を検知するListenerをセット(任意)
  • playWhenReadyで再生・停止の状態を設定 (ここでは初期化のみのためfalseを指定しています)

とします

再生

onClickされた際にplayWhenReadytrueにします

snippet.kt
audio.setOnClickListener {
    audioPlayer?.playWhenReady = true
}

リリース

snippet.kt
private fun releasePlayer() {
    audioPlayer?.let {
        it.stop()
        it.release()
        audioPlayer = null
    }
}

使わなくなったら停止・リリースしましょう
onPauseonDestroyでよく使われます

おわりに

これで音声のみ簡単に再生させることができます

参考になれば幸いです

こちらのレポジトリから簡単にお試しできます

他初心者向けの参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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にする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.kt
package 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

参考

 HTML to text with custom on link click listener

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む