Update dependecies.
- firebase-messaging:20.3.0 - conscrypt-android:2.5.1 - exoplayer:2.12.0 - junit:4.13.1 - com.android.tools.build:gradle:4.1.0 - com.google.gms:google-services:4.3.4 - de.mobilej.unmock:UnMockPlugin:0.7.6 Update ActMediaViewer for exoPlayer API changes.
This commit is contained in:
parent
54c03d2b48
commit
bc1621deaf
|
@ -116,7 +116,7 @@ dependencies {
|
|||
kapt 'androidx.annotation:annotation:1.1.0'
|
||||
|
||||
// https://firebase.google.com/support/release-notes/android
|
||||
implementation "com.google.firebase:firebase-messaging:20.2.4"
|
||||
implementation "com.google.firebase:firebase-messaging:20.3.0"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
@ -152,8 +152,7 @@ dependencies {
|
|||
}
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
|
||||
|
||||
implementation "org.conscrypt:conscrypt-android:2.5.0"
|
||||
implementation "org.conscrypt:conscrypt-android:2.5.1"
|
||||
|
||||
implementation 'io.github.inflationx:calligraphy3:3.1.1'
|
||||
implementation 'io.github.inflationx:viewpump:2.0.3'
|
||||
|
@ -172,7 +171,13 @@ dependencies {
|
|||
|
||||
implementation 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||
|
||||
implementation 'com.google.android.exoplayer:exoplayer:2.10.4'
|
||||
implementation 'com.google.android.exoplayer:exoplayer:2.12.0'
|
||||
/*
|
||||
WARNING: [Processor] Library '…\exoplayer-ui-2.12.0.aar' contains references to both AndroidX and old support library. This seems like the library is partially migrated. Jetifier will try to rewrite the library anyway.
|
||||
Example of androidX reference: 'androidx/core/app/NotificationCompat$Builder'
|
||||
Example of support library reference: 'android/support/v4/media/session/MediaSessionCompat$Token'
|
||||
…expPlayerも苦労してるんだなあ…
|
||||
*/
|
||||
|
||||
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||
}
|
||||
|
|
|
@ -21,13 +21,9 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.Player.TimelineChangeReason
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
|
||||
import com.google.android.exoplayer2.source.MediaSource
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
|
||||
import com.google.android.exoplayer2.source.*
|
||||
import com.google.android.exoplayer2.ui.PlayerView
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||
|
@ -68,22 +64,22 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
internal const val STATE_PLAYER_PLAY_WHEN_READY = "playerPlayWhenReady"
|
||||
internal const val STATE_LAST_VOLUME = "lastVolume"
|
||||
|
||||
internal fun <T : TootAttachmentLike> encodeMediaList(list : ArrayList<T>?) =
|
||||
internal fun <T : TootAttachmentLike> encodeMediaList(list: ArrayList<T>?) =
|
||||
list?.encodeJson()?.toString() ?: "[]"
|
||||
|
||||
internal fun decodeMediaList(src : String?) =
|
||||
internal fun decodeMediaList(src: String?) =
|
||||
ArrayList<TootAttachment>().apply {
|
||||
src?.decodeJsonArray()?.forEach {
|
||||
if(it !is JsonObject) return@forEach
|
||||
if (it !is JsonObject) return@forEach
|
||||
add(TootAttachment.decodeJson(it))
|
||||
}
|
||||
}
|
||||
|
||||
fun open(
|
||||
activity : ActMain,
|
||||
serviceType : ServiceType,
|
||||
list : ArrayList<TootAttachmentLike>,
|
||||
idx : Int
|
||||
activity: ActMain,
|
||||
serviceType: ServiceType,
|
||||
list: ArrayList<TootAttachmentLike>,
|
||||
idx: Int
|
||||
) {
|
||||
val intent = Intent(activity, ActMediaViewer::class.java)
|
||||
intent.putExtra(EXTRA_IDX, idx)
|
||||
|
@ -94,59 +90,50 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
internal var idx : Int = 0
|
||||
private lateinit var media_list : ArrayList<TootAttachment>
|
||||
private lateinit var serviceType : ServiceType
|
||||
internal var idx: Int = 0
|
||||
private lateinit var media_list: ArrayList<TootAttachment>
|
||||
private lateinit var serviceType: ServiceType
|
||||
|
||||
private lateinit var pbvImage : PinchBitmapView
|
||||
private lateinit var btnPrevious : View
|
||||
private lateinit var btnNext : View
|
||||
private lateinit var tvError : TextView
|
||||
private lateinit var exoPlayer : SimpleExoPlayer
|
||||
private lateinit var exoView : PlayerView
|
||||
private lateinit var svDescription : View
|
||||
private lateinit var tvDescription : TextView
|
||||
private lateinit var tvStatus : TextView
|
||||
private lateinit var cbMute : CheckBox
|
||||
private lateinit var pbvImage: PinchBitmapView
|
||||
private lateinit var btnPrevious: View
|
||||
private lateinit var btnNext: View
|
||||
private lateinit var tvError: TextView
|
||||
private lateinit var exoPlayer: SimpleExoPlayer
|
||||
private lateinit var exoView: PlayerView
|
||||
private lateinit var svDescription: View
|
||||
private lateinit var tvDescription: TextView
|
||||
private lateinit var tvStatus: TextView
|
||||
private lateinit var cbMute: CheckBox
|
||||
private var lastVolume = Float.NaN
|
||||
|
||||
internal var buffering_last_shown : Long = 0
|
||||
internal var buffering_last_shown: Long = 0
|
||||
|
||||
private val player_listener = object : Player.EventListener {
|
||||
|
||||
override fun onTimelineChanged(
|
||||
timeline : Timeline?,
|
||||
manifest : Any?,
|
||||
reason : Int
|
||||
timeline: Timeline,
|
||||
@TimelineChangeReason reason: Int
|
||||
) {
|
||||
log.d("exoPlayer onTimelineChanged manifest=$manifest reason=$reason")
|
||||
log.d("exoPlayer onTimelineChanged reason=$reason")
|
||||
}
|
||||
|
||||
override fun onSeekProcessed() {
|
||||
}
|
||||
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled : Boolean) {
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
}
|
||||
|
||||
override fun onTracksChanged(
|
||||
trackGroups : TrackGroupArray?,
|
||||
trackSelections : TrackSelectionArray?
|
||||
) {
|
||||
log.d("exoPlayer onTracksChanged")
|
||||
|
||||
}
|
||||
|
||||
override fun onLoadingChanged(isLoading : Boolean) {
|
||||
override fun onLoadingChanged(isLoading: Boolean) {
|
||||
// かなり頻繁に呼ばれる
|
||||
// warning.d( "exoPlayer onLoadingChanged %s" ,isLoading );
|
||||
}
|
||||
|
||||
override fun onPlayerStateChanged(playWhenReady : Boolean, playbackState : Int) {
|
||||
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||
// かなり頻繁に呼ばれる
|
||||
// warning.d( "exoPlayer onPlayerStateChanged %s %s", playWhenReady, playbackState );
|
||||
if(playWhenReady && playbackState == Player.STATE_BUFFERING) {
|
||||
if (playWhenReady && playbackState == Player.STATE_BUFFERING) {
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
if(now - buffering_last_shown >= short_limit && exoPlayer.duration >= short_limit) {
|
||||
if (now - buffering_last_shown >= short_limit && exoPlayer.duration >= short_limit) {
|
||||
buffering_last_shown = now
|
||||
showToast(false, R.string.video_buffering)
|
||||
}
|
||||
|
@ -156,26 +143,21 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode : Int) {
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
log.d("exoPlayer onRepeatModeChanged %d", repeatMode)
|
||||
}
|
||||
|
||||
override fun onPlayerError(error : ExoPlaybackException) {
|
||||
override fun onPlayerError(error: ExoPlaybackException) {
|
||||
log.d("exoPlayer onPlayerError")
|
||||
showToast(error, "player error.")
|
||||
}
|
||||
|
||||
override fun onPositionDiscontinuity(reason : Int) {
|
||||
override fun onPositionDiscontinuity(reason: Int) {
|
||||
log.d("exoPlayer onPositionDiscontinuity reason=$reason")
|
||||
}
|
||||
|
||||
override fun onPlaybackParametersChanged(playbackParameters : PlaybackParameters?) {
|
||||
log.d("exoPlayer onPlaybackParametersChanged")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState : Bundle) {
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
log.d("onSaveInstanceState")
|
||||
|
@ -189,7 +171,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
outState.putFloat(STATE_LAST_VOLUME, lastVolume)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState : Bundle?) {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
App1.setActivityTheme(this, noActionBar = true, forceDark = true)
|
||||
|
@ -208,7 +190,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
?: intent.getStringExtra(EXTRA_DATA)
|
||||
)
|
||||
|
||||
if(idx < 0 || idx >= media_list.size) idx = 0
|
||||
if (idx < 0 || idx >= media_list.size) idx = 0
|
||||
|
||||
initUI()
|
||||
|
||||
|
@ -243,8 +225,8 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
val enablePaging = media_list.size > 1
|
||||
btnPrevious.isEnabled = enablePaging
|
||||
btnNext.isEnabled = enablePaging
|
||||
btnPrevious.alpha = if(enablePaging) 1f else 0.3f
|
||||
btnNext.alpha = if(enablePaging) 1f else 0.3f
|
||||
btnPrevious.alpha = if (enablePaging) 1f else 0.3f
|
||||
btnNext.alpha = if (enablePaging) 1f else 0.3f
|
||||
|
||||
btnPrevious.setOnClickListener(this)
|
||||
btnNext.setOnClickListener(this)
|
||||
|
@ -252,7 +234,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
findViewById<View>(R.id.btnMore).setOnClickListener(this)
|
||||
|
||||
cbMute.setOnCheckedChangeListener { _, isChecked ->
|
||||
if(isChecked) {
|
||||
if (isChecked) {
|
||||
// mute
|
||||
lastVolume = exoPlayer.volume
|
||||
exoPlayer.volume = 0f
|
||||
|
@ -268,9 +250,9 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
pbvImage.setCallback(object : PinchBitmapView.Callback {
|
||||
override fun onSwipe(deltaX : Int, deltaY : Int) {
|
||||
if(isDestroyed) return
|
||||
if(deltaX != 0) {
|
||||
override fun onSwipe(deltaX: Int, deltaY: Int) {
|
||||
if (isDestroyed) return
|
||||
if (deltaX != 0) {
|
||||
loadDelta(deltaX)
|
||||
} else {
|
||||
log.d("finish by vertical swipe")
|
||||
|
@ -279,15 +261,15 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
override fun onMove(
|
||||
bitmap_w : Float,
|
||||
bitmap_h : Float,
|
||||
tx : Float,
|
||||
ty : Float,
|
||||
scale : Float
|
||||
bitmap_w: Float,
|
||||
bitmap_h: Float,
|
||||
tx: Float,
|
||||
ty: Float,
|
||||
scale: Float
|
||||
) {
|
||||
App1.getAppState(this@ActMediaViewer).handler.post(Runnable {
|
||||
if(isDestroyed) return@Runnable
|
||||
if(tvStatus.visibility == View.VISIBLE) {
|
||||
if (isDestroyed) return@Runnable
|
||||
if (tvStatus.visibility == View.VISIBLE) {
|
||||
tvStatus.text = getString(
|
||||
R.string.zooming_of,
|
||||
bitmap_w.toInt(),
|
||||
|
@ -299,20 +281,19 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
})
|
||||
|
||||
exoPlayer = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector())
|
||||
exoPlayer = SimpleExoPlayer.Builder(this).build()
|
||||
exoPlayer.addListener(player_listener)
|
||||
|
||||
exoView.player = exoPlayer
|
||||
}
|
||||
|
||||
internal fun loadDelta(delta : Int) {
|
||||
if(media_list.size < 2) return
|
||||
internal fun loadDelta(delta: Int) {
|
||||
if (media_list.size < 2) return
|
||||
val size = media_list.size
|
||||
idx = (idx + size + delta) % size
|
||||
load()
|
||||
}
|
||||
|
||||
internal fun load(state : Bundle? = null) {
|
||||
internal fun load(state: Bundle? = null) {
|
||||
|
||||
exoPlayer.stop()
|
||||
pbvImage.visibility = View.GONE
|
||||
|
@ -321,18 +302,18 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
svDescription.visibility = View.GONE
|
||||
tvStatus.visibility = View.GONE
|
||||
|
||||
if(idx < 0 || idx >= media_list.size) {
|
||||
if (idx < 0 || idx >= media_list.size) {
|
||||
showError(getString(R.string.media_attachment_empty))
|
||||
return
|
||||
}
|
||||
val ta = media_list[idx]
|
||||
val description = ta.description
|
||||
if(description?.isNotEmpty() == true) {
|
||||
if (description?.isNotEmpty() == true) {
|
||||
svDescription.visibility = View.VISIBLE
|
||||
tvDescription.text = description
|
||||
}
|
||||
|
||||
when(ta.type) {
|
||||
when (ta.type) {
|
||||
|
||||
TootAttachmentType.Unknown ->
|
||||
showError(getString(R.string.media_attachment_type_error, ta.type.id))
|
||||
|
@ -348,7 +329,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
}
|
||||
|
||||
private fun showError(message : String) {
|
||||
private fun showError(message: String) {
|
||||
exoView.visibility = View.GONE
|
||||
pbvImage.visibility = View.GONE
|
||||
tvError.visibility = View.VISIBLE
|
||||
|
@ -357,15 +338,15 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private fun loadVideo(ta : TootAttachment, state : Bundle? = null) {
|
||||
private fun loadVideo(ta: TootAttachment, state: Bundle? = null) {
|
||||
|
||||
cbMute.vg(true)
|
||||
if(cbMute.isChecked && lastVolume.isFinite()) {
|
||||
if (cbMute.isChecked && lastVolume.isFinite()) {
|
||||
exoPlayer.volume = 0f
|
||||
}
|
||||
|
||||
val url = ta.getLargeUrl(App1.pref)
|
||||
if(url == null) {
|
||||
if (url == null) {
|
||||
showError("missing media attachment url.")
|
||||
return
|
||||
}
|
||||
|
@ -383,17 +364,18 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
)
|
||||
|
||||
val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(url.toUri())
|
||||
.createMediaSource(MediaItem.Builder().setUri(url.toUri()).build())
|
||||
|
||||
mediaSource.addEventListener(App1.getAppState(this).handler, mediaSourceEventListener)
|
||||
|
||||
exoPlayer.prepare(mediaSource)
|
||||
exoPlayer.repeatMode = when(ta.type) {
|
||||
exoPlayer.setMediaSource(mediaSource)
|
||||
exoPlayer.prepare()
|
||||
exoPlayer.repeatMode = when (ta.type) {
|
||||
TootAttachmentType.Video -> Player.REPEAT_MODE_OFF
|
||||
// GIFV or AUDIO
|
||||
else -> Player.REPEAT_MODE_ALL
|
||||
}
|
||||
if(state == null) {
|
||||
if (state == null) {
|
||||
exoPlayer.playWhenReady = true
|
||||
} else {
|
||||
exoPlayer.playWhenReady = state.getBoolean(STATE_PLAYER_PLAY_WHEN_READY, true)
|
||||
|
@ -404,91 +386,67 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
private val mediaSourceEventListener = object : MediaSourceEventListener {
|
||||
override fun onLoadStarted(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?,
|
||||
loadEventInfo : MediaSourceEventListener.LoadEventInfo?,
|
||||
mediaLoadData : MediaSourceEventListener.MediaLoadData?
|
||||
windowIndex: Int,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||
loadEventInfo: LoadEventInfo,
|
||||
mediaLoadData: MediaLoadData
|
||||
) {
|
||||
log.d("onLoadStarted")
|
||||
}
|
||||
|
||||
override fun onDownstreamFormatChanged(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?,
|
||||
mediaLoadData : MediaSourceEventListener.MediaLoadData?
|
||||
windowIndex: Int,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||
mediaLoadData: MediaLoadData
|
||||
) {
|
||||
log.d("onDownstreamFormatChanged")
|
||||
}
|
||||
|
||||
override fun onUpstreamDiscarded(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?,
|
||||
mediaLoadData : MediaSourceEventListener.MediaLoadData?
|
||||
windowIndex: Int,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId,
|
||||
mediaLoadData: MediaLoadData
|
||||
) {
|
||||
log.d("onUpstreamDiscarded")
|
||||
}
|
||||
|
||||
override fun onLoadCompleted(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?,
|
||||
loadEventInfo : MediaSourceEventListener.LoadEventInfo?,
|
||||
mediaLoadData : MediaSourceEventListener.MediaLoadData?
|
||||
windowIndex: Int,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||
loadEventInfo: LoadEventInfo,
|
||||
mediaLoadData: MediaLoadData
|
||||
) {
|
||||
log.d("onLoadCompleted")
|
||||
}
|
||||
|
||||
override fun onLoadCanceled(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?,
|
||||
loadEventInfo : MediaSourceEventListener.LoadEventInfo?,
|
||||
mediaLoadData : MediaSourceEventListener.MediaLoadData?
|
||||
windowIndex: Int,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||
loadEventInfo: LoadEventInfo,
|
||||
mediaLoadData: MediaLoadData
|
||||
) {
|
||||
log.d("onLoadCanceled")
|
||||
}
|
||||
|
||||
override fun onLoadError(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?,
|
||||
loadEventInfo : MediaSourceEventListener.LoadEventInfo?,
|
||||
mediaLoadData : MediaSourceEventListener.MediaLoadData?,
|
||||
error : IOException?,
|
||||
wasCanceled : Boolean
|
||||
windowIndex: Int,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||
loadEventInfo: LoadEventInfo,
|
||||
mediaLoadData: MediaLoadData,
|
||||
error: IOException,
|
||||
wasCanceled: Boolean
|
||||
) {
|
||||
if(error != null) {
|
||||
showError(error.withCaption("load error."))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMediaPeriodCreated(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?
|
||||
) {
|
||||
log.d("onMediaPeriodCreated")
|
||||
}
|
||||
|
||||
override fun onMediaPeriodReleased(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?
|
||||
) {
|
||||
log.d("onMediaPeriodReleased")
|
||||
}
|
||||
|
||||
override fun onReadingStarted(
|
||||
windowIndex : Int,
|
||||
mediaPeriodId : MediaSource.MediaPeriodId?
|
||||
) {
|
||||
log.d("onReadingStarted")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private fun loadBitmap(ta : TootAttachment) {
|
||||
private fun loadBitmap(ta: TootAttachment) {
|
||||
|
||||
cbMute.visibility = View.INVISIBLE
|
||||
|
||||
val urlList = ta.getLargeUrlList(App1.pref)
|
||||
if(urlList.isEmpty()) {
|
||||
if (urlList.isEmpty()) {
|
||||
showError("missing media attachment url.")
|
||||
return
|
||||
}
|
||||
|
@ -503,14 +461,14 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
private val options = BitmapFactory.Options()
|
||||
|
||||
var bitmap : Bitmap? = null
|
||||
var bitmap: Bitmap? = null
|
||||
|
||||
private fun decodeBitmap(
|
||||
data : ByteArray,
|
||||
@Suppress("SameParameterValue") pixel_max : Int
|
||||
) : Pair<Bitmap?, String?> {
|
||||
data: ByteArray,
|
||||
@Suppress("SameParameterValue") pixel_max: Int
|
||||
): Pair<Bitmap?, String?> {
|
||||
|
||||
val orientation : Int? = ByteArrayInputStream(data).imageOrientation()
|
||||
val orientation: Int? = ByteArrayInputStream(data).imageOrientation()
|
||||
|
||||
// detects image size
|
||||
options.inJustDecodeBounds = true
|
||||
|
@ -520,14 +478,14 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
||||
var w = options.outWidth
|
||||
var h = options.outHeight
|
||||
if(w <= 0 || h <= 0) {
|
||||
if (w <= 0 || h <= 0) {
|
||||
return Pair(null, "can't decode image bounds.")
|
||||
}
|
||||
|
||||
// calc bits to reduce size
|
||||
var bits = 0
|
||||
while(w > pixel_max || h > pixel_max) {
|
||||
++ bits
|
||||
while (w > pixel_max || h > pixel_max) {
|
||||
++bits
|
||||
w = w shr 1
|
||||
h = h shr 1
|
||||
}
|
||||
|
@ -540,7 +498,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
val srcWidth = bitmap1.width.toFloat()
|
||||
val srcHeight = bitmap1.height.toFloat()
|
||||
if(srcWidth <= 0f || srcHeight <= 0f) {
|
||||
if (srcWidth <= 0f || srcHeight <= 0f) {
|
||||
bitmap1.recycle()
|
||||
return Pair(null, "image size <= 0")
|
||||
}
|
||||
|
@ -556,7 +514,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
matrix.reset()
|
||||
|
||||
// 画像の中心が原点に来るようにして
|
||||
matrix.postTranslate(srcWidth * - 0.5f, srcHeight * - 0.5f)
|
||||
matrix.postTranslate(srcWidth * -0.5f, srcHeight * -0.5f)
|
||||
|
||||
// orientationに合わせた回転指定
|
||||
matrix.resolveOrientation(orientation)
|
||||
|
@ -568,7 +526,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
val bitmap2 = try {
|
||||
Bitmap.createBitmap(dstSizeInt.x, dstSizeInt.y, Bitmap.Config.ARGB_8888)
|
||||
?: return Pair(bitmap1, "createBitmap returns null")
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
log.trace(ex)
|
||||
return Pair(bitmap1, ex.withCaption("createBitmap failed."))
|
||||
}
|
||||
|
@ -579,7 +537,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
matrix,
|
||||
Paint().apply { isFilterBitmap = true }
|
||||
)
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
log.trace(ex)
|
||||
bitmap2.recycle()
|
||||
return Pair(bitmap1, ex.withCaption("drawBitmap failed."))
|
||||
|
@ -587,15 +545,15 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
try {
|
||||
bitmap1.recycle()
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
}
|
||||
return Pair(bitmap2, null)
|
||||
}
|
||||
|
||||
fun getHttpCached(
|
||||
client : TootApiClient,
|
||||
url : String
|
||||
) : Pair<TootApiResult?, ByteArray?> {
|
||||
client: TootApiClient,
|
||||
url: String
|
||||
): Pair<TootApiResult?, ByteArray?> {
|
||||
val result = TootApiResult.makeWithCaption(url)
|
||||
|
||||
val request = try {
|
||||
|
@ -604,12 +562,12 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
.cacheControl(App1.CACHE_CONTROL)
|
||||
.addHeader("Accept", "image/webp,image/*,*/*;q=0.8")
|
||||
.build()
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
result.setError(ex.withCaption("incorrect URL."))
|
||||
return Pair(result, null)
|
||||
}
|
||||
|
||||
if(! client.sendRequest(
|
||||
if (!client.sendRequest(
|
||||
result,
|
||||
tmpOkhttpClient = App1.ok_http_client_media_viewer
|
||||
) {
|
||||
|
@ -617,10 +575,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
) return Pair(result, null)
|
||||
|
||||
if(client.isApiCancelled) return Pair(null, null)
|
||||
if (client.isApiCancelled) return Pair(null, null)
|
||||
|
||||
val response = requireNotNull(result.response)
|
||||
if(! response.isSuccessful) {
|
||||
if (!response.isSuccessful) {
|
||||
result.setError(TootApiClient.formatResponse(response, result.caption))
|
||||
return Pair(result, null)
|
||||
}
|
||||
|
@ -628,44 +586,44 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
try {
|
||||
val ba = ProgressResponseBody.bytes(response) { bytesRead, bytesTotal ->
|
||||
// 50MB以上のデータはキャンセルする
|
||||
if(max(bytesRead, bytesTotal) >= 50000000) {
|
||||
if (max(bytesRead, bytesTotal) >= 50000000) {
|
||||
throw RuntimeException("media attachment is larger than 50000000")
|
||||
}
|
||||
client.publishApiProgressRatio(bytesRead.toInt(), bytesTotal.toInt())
|
||||
}
|
||||
if(client.isApiCancelled) return Pair(null, null)
|
||||
if (client.isApiCancelled) return Pair(null, null)
|
||||
return Pair(result, ba)
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
result.setError(TootApiClient.formatResponse(response, result.caption, "?"))
|
||||
return Pair(result, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
if(urlList.isEmpty()) return TootApiResult("missing url")
|
||||
var lastResult : TootApiResult? = null
|
||||
for(url in urlList) {
|
||||
override fun background(client: TootApiClient): TootApiResult? {
|
||||
if (urlList.isEmpty()) return TootApiResult("missing url")
|
||||
var lastResult: TootApiResult? = null
|
||||
for (url in urlList) {
|
||||
val (result, ba) = getHttpCached(client, url)
|
||||
lastResult = result
|
||||
if(ba != null) {
|
||||
if (ba != null) {
|
||||
client.publishApiProgress("decoding image…")
|
||||
|
||||
val (bitmap, error) = decodeBitmap(ba, 2048)
|
||||
if(bitmap != null) {
|
||||
if (bitmap != null) {
|
||||
this.bitmap = bitmap
|
||||
break
|
||||
}
|
||||
if(error != null) lastResult = TootApiResult(error)
|
||||
if (error != null) lastResult = TootApiResult(error)
|
||||
}
|
||||
}
|
||||
return lastResult
|
||||
}
|
||||
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
override fun handleResult(result: TootApiResult?) {
|
||||
val bitmap = this.bitmap
|
||||
if(bitmap != null) {
|
||||
if (bitmap != null) {
|
||||
pbvImage.setBitmap(bitmap)
|
||||
} else if(result != null) {
|
||||
} else if (result != null) {
|
||||
showToast(true, result.error)
|
||||
}
|
||||
}
|
||||
|
@ -673,38 +631,38 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
}
|
||||
|
||||
override fun onClick(v : View) {
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
when(v.id) {
|
||||
when (v.id) {
|
||||
|
||||
R.id.btnPrevious -> loadDelta(- 1)
|
||||
R.id.btnNext -> loadDelta(+ 1)
|
||||
R.id.btnPrevious -> loadDelta(-1)
|
||||
R.id.btnNext -> loadDelta(+1)
|
||||
R.id.btnDownload -> download(media_list[idx])
|
||||
R.id.btnMore -> more(media_list[idx])
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "action failed.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class DownloadHistory(val time : Long, val url : String)
|
||||
internal class DownloadHistory(val time: Long, val url: String)
|
||||
|
||||
private fun download(ta : TootAttachmentLike) {
|
||||
private fun download(ta: TootAttachmentLike) {
|
||||
|
||||
val permissionCheck = ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
if(permissionCheck != PackageManager.PERMISSION_GRANTED) {
|
||||
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
|
||||
preparePermission()
|
||||
return
|
||||
}
|
||||
|
||||
val downLoadManager : DownloadManager = systemService(this)
|
||||
val downLoadManager: DownloadManager = systemService(this)
|
||||
?: error("missing DownloadManager system service")
|
||||
|
||||
val url = if(ta is TootAttachment) {
|
||||
val url = if (ta is TootAttachment) {
|
||||
ta.getLargeUrl(App1.pref)
|
||||
} else {
|
||||
null
|
||||
|
@ -716,12 +674,12 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
// 期限切れの履歴を削除
|
||||
val it = download_history_list.iterator()
|
||||
while(it.hasNext()) {
|
||||
while (it.hasNext()) {
|
||||
val dh = it.next()
|
||||
if(now - dh.time >= DOWNLOAD_REPEAT_EXPIRE) {
|
||||
if (now - dh.time >= DOWNLOAD_REPEAT_EXPIRE) {
|
||||
// この履歴は十分に古いので捨てる
|
||||
it.remove()
|
||||
} else if(url == dh.url) {
|
||||
} else if (url == dh.url) {
|
||||
// 履歴に同じURLがあればエラーとする
|
||||
showToast(false, R.string.dont_repeat_download_to_same_url)
|
||||
return
|
||||
|
@ -731,30 +689,30 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
download_history_list.addLast(DownloadHistory(now, url))
|
||||
}
|
||||
|
||||
var fileName : String? = null
|
||||
var fileName: String? = null
|
||||
|
||||
try {
|
||||
val pathSegments = url.toUri().pathSegments
|
||||
if(pathSegments != null) {
|
||||
if (pathSegments != null) {
|
||||
val size = pathSegments.size
|
||||
for(i in size - 1 downTo 0) {
|
||||
for (i in size - 1 downTo 0) {
|
||||
val s = pathSegments[i]
|
||||
if(s?.isNotEmpty() == true) {
|
||||
if (s?.isNotEmpty() == true) {
|
||||
fileName = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
||||
if(fileName == null) {
|
||||
if (fileName == null) {
|
||||
fileName = url
|
||||
.replaceFirst("https?://".asciiPattern(), "")
|
||||
.replaceAll("[^.\\w\\d]+".asciiPattern(), "-")
|
||||
}
|
||||
if(fileName.length >= 20) fileName = fileName.substring(fileName.length - 20)
|
||||
if (fileName.length >= 20) fileName = fileName.substring(fileName.length - 20)
|
||||
|
||||
val request = DownloadManager.Request(url.toUri())
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
|
||||
|
@ -762,7 +720,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI)
|
||||
|
||||
// Android 10 以降では allowScanningByMediaScanner は無視される
|
||||
if(Build.VERSION.SDK_INT < 29) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
//メディアスキャンを許可する
|
||||
@Suppress("DEPRECATION")
|
||||
request.allowScanningByMediaScanner()
|
||||
|
@ -775,12 +733,12 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
showToast(false, R.string.downloading)
|
||||
}
|
||||
|
||||
private fun share(action : String, url : String) {
|
||||
private fun share(action: String, url: String) {
|
||||
|
||||
try {
|
||||
val intent = Intent(action)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if(action == Intent.ACTION_SEND) {
|
||||
if (action == Intent.ACTION_SEND) {
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url)
|
||||
} else {
|
||||
|
@ -788,13 +746,13 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
startActivity(intent)
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "can't open app.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun copy(url : String) {
|
||||
internal fun copy(url: String) {
|
||||
val cm = getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager
|
||||
?: throw NotImplementedError("missing ClipboardManager system service")
|
||||
|
||||
|
@ -813,16 +771,16 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
showToast(false, R.string.url_is_copied)
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "clipboard access failed.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun more(ta : TootAttachmentLike) {
|
||||
internal fun more(ta: TootAttachmentLike) {
|
||||
val ad = ActionsDialog()
|
||||
|
||||
if(ta is TootAttachment) {
|
||||
if (ta is TootAttachment) {
|
||||
val url = ta.getLargeUrl(App1.pref) ?: return
|
||||
|
||||
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
||||
|
@ -835,7 +793,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
addMoreMenu(ad, "preview_remote_url", ta.preview_remote_url, Intent.ACTION_VIEW)
|
||||
addMoreMenu(ad, "text_url", ta.text_url, Intent.ACTION_VIEW)
|
||||
|
||||
} else if(ta is TootAttachmentMSP) {
|
||||
} else if (ta is TootAttachmentMSP) {
|
||||
val url = ta.preview_url
|
||||
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
||||
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
|
||||
|
@ -846,10 +804,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
private fun addMoreMenu(
|
||||
ad : ActionsDialog,
|
||||
caption_prefix : String,
|
||||
url : String?,
|
||||
@Suppress("SameParameterValue") action : String
|
||||
ad: ActionsDialog,
|
||||
caption_prefix: String,
|
||||
url: String?,
|
||||
@Suppress("SameParameterValue") action: String
|
||||
) {
|
||||
val uri = url.mayUri() ?: return
|
||||
|
||||
|
@ -860,14 +818,14 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
val intent = Intent(action, uri)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
} catch(ex : Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "can't open app.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun preparePermission() {
|
||||
if(Build.VERSION.SDK_INT >= 23) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE
|
||||
)
|
||||
|
@ -877,20 +835,20 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode : Int, permissions : Array<String>, grantResults : IntArray
|
||||
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
||||
) {
|
||||
when(requestCode) {
|
||||
when (requestCode) {
|
||||
PERMISSION_REQUEST_CODE -> {
|
||||
var bNotGranted = false
|
||||
var i = 0
|
||||
val ie = permissions.size
|
||||
while(i < ie) {
|
||||
if(grantResults[i] != PackageManager.PERMISSION_GRANTED) {
|
||||
while (i < ie) {
|
||||
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
|
||||
bNotGranted = true
|
||||
}
|
||||
++ i
|
||||
++i
|
||||
}
|
||||
if(bNotGranted) {
|
||||
if (bNotGranted) {
|
||||
showToast(true, R.string.missing_permission_to_access_media)
|
||||
} else {
|
||||
download(media_list[idx])
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@drawable/media_background" />
|
||||
|
||||
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
android:id="@+id/exoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
|
|
@ -8,7 +8,7 @@ buildscript {
|
|||
ext.kotlinx_coroutines_version = '1.3.9'
|
||||
ext.anko_version='0.10.8'
|
||||
|
||||
ext.junit_version='4.13'
|
||||
ext.junit_version='4.13.1'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
|
@ -17,12 +17,12 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath 'com.google.gms:google-services:4.3.4'
|
||||
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
classpath 'de.mobilej.unmock:UnMockPlugin:0.7.3'
|
||||
classpath 'de.mobilej.unmock:UnMockPlugin:0.7.6'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue