BuildConfigを排除。添付データのアップロードにチャネルを2つ使ってたのをやめる
This commit is contained in:
parent
1612b7cffe
commit
f5accd5ffa
|
@ -4,30 +4,24 @@
|
|||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.unitTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.unitTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.main.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.unitTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.main.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.unitTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.main.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.unitTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/icon_material_symbols/SubwayTooter.icon_material_symbols.iml" filepath="$PROJECT_DIR$/.idea/modules/icon_material_symbols/SubwayTooter.icon_material_symbols.iml" />
|
||||
|
|
|
@ -41,7 +41,6 @@ android {
|
|||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
|
@ -96,15 +95,11 @@ android {
|
|||
dimension "fcmType"
|
||||
versionNameSuffix "-noFcm"
|
||||
applicationIdSuffix ".noFcm"
|
||||
def scheme = "subwaytooternofcm"
|
||||
manifestPlaceholders = [customScheme: scheme]
|
||||
buildConfigField("String", "customScheme", "\"$scheme\"")
|
||||
manifestPlaceholders = [customScheme: "subwaytooternofcm"]
|
||||
}
|
||||
fcm {
|
||||
dimension "fcmType"
|
||||
def scheme = "subwaytooter"
|
||||
manifestPlaceholders = [customScheme: scheme]
|
||||
buildConfigField("String", "customScheme", "\"$scheme\"")
|
||||
manifestPlaceholders = [customScheme: "subwaytooter"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
object ReleaseType {
|
||||
const val isDebug = true
|
||||
const val isRelease = !isDebug
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package jp.juggler.subwaytooter.push
|
||||
|
||||
object FcmFlavor {
|
||||
const val APPLICATION_ID = "jp.juggler.subwaytooter"
|
||||
const val CUSTOM_SCHEME = "subwaytooter"
|
||||
}
|
|
@ -52,7 +52,6 @@ import jp.juggler.util.network.toPost
|
|||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.ui.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
|
@ -271,7 +270,7 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
setSwitchColor(views.root)
|
||||
|
||||
views.apply {
|
||||
btnPushSubscriptionNotForce.vg(BuildConfig.DEBUG)
|
||||
btnPushSubscriptionNotForce.vg(ReleaseType.isDebug)
|
||||
|
||||
imageResizeItems = SavedAccount.resizeConfigList.map {
|
||||
val caption = when (it.type) {
|
||||
|
|
|
@ -691,6 +691,8 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
etEditText.inputType = item.inputType
|
||||
etEditText.setText(text)
|
||||
etEditText.setSelection(0, text.length)
|
||||
|
||||
item.showEditText.invoke(actAppSetting,views.etEditText)
|
||||
}
|
||||
updateErrorView()
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import jp.juggler.subwaytooter.actpost.CompletionHelper
|
|||
import jp.juggler.subwaytooter.actpost.FeaturedTagCache
|
||||
import jp.juggler.subwaytooter.actpost.addAttachment
|
||||
import jp.juggler.subwaytooter.actpost.applyMushroomText
|
||||
import jp.juggler.subwaytooter.actpost.launchAddAttachmentChannelReader
|
||||
import jp.juggler.subwaytooter.actpost.onPickCustomThumbnailImpl
|
||||
import jp.juggler.subwaytooter.actpost.onPostAttachmentCompleteImpl
|
||||
import jp.juggler.subwaytooter.actpost.openAttachment
|
||||
|
@ -198,13 +197,6 @@ class ActPost : AppCompatActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
class AddAttachmentChannelItem(
|
||||
val uri: Uri,
|
||||
val mimeTypeArg: String?,
|
||||
)
|
||||
|
||||
val addAttachmentChannel = Channel<AddAttachmentChannelItem>(capacity = Channel.BUFFERED)
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -228,8 +220,6 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
progressChannel = Channel(capacity = Channel.CONFLATED)
|
||||
|
||||
launchAddAttachmentChannelReader()
|
||||
|
||||
initUI()
|
||||
|
||||
// 進捗表示チャネルの回収コルーチン
|
||||
|
@ -265,7 +255,6 @@ class ActPost : AppCompatActivity(),
|
|||
}
|
||||
completionHelper.onDestroy()
|
||||
attachmentUploader.onActivityDestroy()
|
||||
addAttachmentChannel.close()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
|||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
@ -28,8 +27,8 @@ import jp.juggler.subwaytooter.table.SavedAccount
|
|||
import jp.juggler.subwaytooter.util.CustomEmojiCache
|
||||
import jp.juggler.subwaytooter.util.CustomEmojiLister
|
||||
import jp.juggler.subwaytooter.util.ProgressResponseBody
|
||||
import jp.juggler.subwaytooter.util.getUserAgent
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.data.asciiPattern
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.initializeToastUtils
|
||||
|
@ -134,22 +133,10 @@ class App1 : Application() {
|
|||
// return maxSize * 1024;
|
||||
// }
|
||||
|
||||
val reNotAllowedInUserAgent = "[^\\x21-\\x7e]+".asciiPattern()
|
||||
private var cookieManager: CookieManager? = null
|
||||
private var cookieJar: CookieJar? = null
|
||||
|
||||
val userAgentDefault =
|
||||
"SubwayTooter/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE}"
|
||||
|
||||
private fun getUserAgent(): String {
|
||||
val userAgentCustom = PrefS.spUserAgent.value
|
||||
return when {
|
||||
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
|
||||
.find() -> userAgentCustom
|
||||
|
||||
else -> userAgentDefault
|
||||
}
|
||||
}
|
||||
|
||||
private fun userAgentInterceptor() =
|
||||
private fun Context.userAgentInterceptor() =
|
||||
Interceptor { chain ->
|
||||
chain.proceed(
|
||||
chain.request().newBuilder()
|
||||
|
@ -158,17 +145,14 @@ class App1 : Application() {
|
|||
)
|
||||
}
|
||||
|
||||
private var cookieManager: CookieManager? = null
|
||||
private var cookieJar: CookieJar? = null
|
||||
|
||||
private fun prepareOkHttp(
|
||||
private fun Context.prepareOkHttp(
|
||||
timeoutSecondsConnect: Int,
|
||||
timeoutSecondsRead: Int,
|
||||
): OkHttpClient.Builder {
|
||||
|
||||
Logger.getLogger(OkHttpClient::class.java.name).level = Level.FINE
|
||||
|
||||
var cookieJar = this.cookieJar
|
||||
var cookieJar = this@Companion.cookieJar
|
||||
if (cookieJar == null) {
|
||||
val cookieManager = CookieManager().apply {
|
||||
setCookiePolicy(CookiePolicy.ACCEPT_ALL)
|
||||
|
@ -176,8 +160,8 @@ class App1 : Application() {
|
|||
CookieHandler.setDefault(cookieManager)
|
||||
cookieJar = JavaNetCookieJar(cookieManager)
|
||||
|
||||
this.cookieManager = cookieManager
|
||||
this.cookieJar = cookieJar
|
||||
this@Companion.cookieManager = cookieManager
|
||||
this@Companion.cookieJar = cookieJar
|
||||
}
|
||||
|
||||
val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||
|
@ -285,7 +269,7 @@ class App1 : Application() {
|
|||
val apiReadTimeout = max(3, PrefS.spApiReadTimeout.toInt())
|
||||
|
||||
// API用のHTTP設定はキャッシュを使わない
|
||||
ok_http_client = prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||
ok_http_client = appContext.prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||
.build()
|
||||
|
||||
// ディスクキャッシュ
|
||||
|
@ -293,14 +277,14 @@ class App1 : Application() {
|
|||
val cache = Cache(cacheDir, 30000000L)
|
||||
|
||||
// カスタム絵文字用のHTTP設定はキャッシュを使う
|
||||
ok_http_client2 = prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||
ok_http_client2 = appContext.prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||
.cache(cache)
|
||||
.build()
|
||||
|
||||
// 内蔵メディアビューア用のHTTP設定はタイムアウトを調整可能
|
||||
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt())
|
||||
ok_http_client_media_viewer =
|
||||
prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
||||
appContext.prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
||||
.cache(cache)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.BuildConfig
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.action.conversationOtherInstance
|
||||
import jp.juggler.subwaytooter.action.openActPostImpl
|
||||
|
@ -32,6 +31,7 @@ import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
|||
import jp.juggler.subwaytooter.notification.recycleClickedNotification
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||
import jp.juggler.subwaytooter.push.fcmHandler
|
||||
import jp.juggler.subwaytooter.push.pushRepo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
|
@ -56,7 +56,7 @@ fun ActMain.handleIntentUri(uri: Uri) {
|
|||
try {
|
||||
log.i("handleIntentUri $uri")
|
||||
when (uri.scheme) {
|
||||
BuildConfig.customScheme -> handleCustomSchemaUri(uri)
|
||||
FcmFlavor.CUSTOM_SCHEME -> handleCustomSchemaUri(uri)
|
||||
else -> handleOtherUri(uri)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -193,7 +193,7 @@ private fun ActMain.handleCustomSchemaUri(uri: Uri) = launchAndShowError {
|
|||
// subwaytooter://oauth(\d*)/?...
|
||||
handleOAuth2Callback(uri)
|
||||
} else {
|
||||
// ${BuildConfig.customScheme}://notification_click/?db_id=(db_id)
|
||||
// ${FcmFlavor.customScheme}://notification_click/?db_id=(db_id)
|
||||
handleNotificationClick(uri, dataIdString)
|
||||
}
|
||||
}
|
||||
|
@ -377,6 +377,7 @@ suspend fun ActMain.updatePushDistributer() {
|
|||
selectPushDistributor()
|
||||
// 選択しなかった場合は購読の更新を行わない
|
||||
}
|
||||
|
||||
else -> {
|
||||
runInProgress(cancellable = false) { reporter ->
|
||||
withContext(AppDispatchers.DEFAULT) {
|
||||
|
|
|
@ -8,16 +8,12 @@ import androidx.appcompat.app.AlertDialog
|
|||
import jp.juggler.subwaytooter.ActPost
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.ApiTask
|
||||
import jp.juggler.subwaytooter.api.TootApiCallback
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.entity.InstanceType
|
||||
import jp.juggler.subwaytooter.api.entity.ServiceType
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachment
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachmentJson
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachmentType
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.parseItem
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.calcIconRound
|
||||
|
@ -28,9 +24,7 @@ import jp.juggler.subwaytooter.dialog.focusPointDialog
|
|||
import jp.juggler.subwaytooter.dialog.showTextInputDialog
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.util.AttachmentRequest
|
||||
import jp.juggler.subwaytooter.util.AttachmentUploader
|
||||
import jp.juggler.subwaytooter.util.PostAttachment
|
||||
import jp.juggler.subwaytooter.util.resolveMimeType
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
|
@ -46,9 +40,6 @@ import jp.juggler.util.log.withCaption
|
|||
import jp.juggler.util.network.toPutRequestBuilder
|
||||
import jp.juggler.util.ui.isLiveActivity
|
||||
import jp.juggler.util.ui.vg
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import kotlin.math.min
|
||||
|
||||
private val log = LogCategory("ActPostAttachment")
|
||||
|
@ -132,140 +123,45 @@ fun ActPost.addAttachment(
|
|||
uri: Uri,
|
||||
mimeTypeArg: String? = null,
|
||||
) {
|
||||
val item = ActPost.AddAttachmentChannelItem(uri = uri, mimeTypeArg = mimeTypeArg)
|
||||
for (nTry in 1..10) {
|
||||
try {
|
||||
val channelResult = addAttachmentChannel.trySend(item)
|
||||
when {
|
||||
channelResult.isSuccess -> return
|
||||
channelResult.isClosed -> return
|
||||
channelResult.isFailure -> continue
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "addAttachmentChannel.trySend failed.")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ActPost.getInstance(): TootInstance {
|
||||
val client = TootApiClient(
|
||||
context = applicationContext,
|
||||
callback = object : TootApiCallback {
|
||||
override suspend fun isApiCancelled() = isFinishing || isDestroyed
|
||||
}
|
||||
).apply {
|
||||
this.account = this@getInstance.account
|
||||
}
|
||||
val (instance, ri) = TootInstance.get(client = client)
|
||||
if (instance != null) return instance
|
||||
when (ri) {
|
||||
null -> throw CancellationException()
|
||||
else -> error("missing instance information. ${ri.error}")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ActPost.addAttachmentSuspend(
|
||||
uri: Uri,
|
||||
mimeTypeArg: String? = null,
|
||||
) {
|
||||
val actPost = this
|
||||
val account = this.account
|
||||
|
||||
val mimeType = uri.resolveMimeType(mimeTypeArg, this)
|
||||
?.notEmpty()
|
||||
|
||||
val isReply = states.inReplyToId != null
|
||||
|
||||
when {
|
||||
actPost.isFinishing || actPost.isDestroyed -> {
|
||||
dialogOrToast("actPost is finishing or destroyed.")
|
||||
return
|
||||
}
|
||||
|
||||
attachmentList.size >= 4 -> {
|
||||
dialogOrToast(R.string.attachment_too_many)
|
||||
return
|
||||
}
|
||||
|
||||
account == null -> {
|
||||
dialogOrToast(R.string.account_select_please)
|
||||
return
|
||||
}
|
||||
|
||||
mimeType == null -> {
|
||||
dialogOrToast(R.string.mime_type_missing)
|
||||
return
|
||||
}
|
||||
if (account == null) {
|
||||
dialogOrToast(R.string.account_select_please)
|
||||
return
|
||||
} else if (attachmentList.size >= 4) {
|
||||
dialogOrToast(R.string.attachment_too_many)
|
||||
return
|
||||
}
|
||||
|
||||
val instance = getInstance()
|
||||
saveAttachmentList()
|
||||
val pa = PostAttachment(this)
|
||||
attachmentList.add(pa)
|
||||
showMediaAttachment()
|
||||
|
||||
when {
|
||||
instance.instanceType == InstanceType.Pixelfed && isReply -> {
|
||||
AttachmentUploader.log.e("pixelfed_does_not_allow_reply_with_media")
|
||||
dialogOrToast(R.string.pixelfed_does_not_allow_reply_with_media)
|
||||
return
|
||||
}
|
||||
|
||||
else -> {
|
||||
saveAttachmentList()
|
||||
val pa = PostAttachment(this)
|
||||
attachmentList.add(pa)
|
||||
showMediaAttachment()
|
||||
val mediaConfig = instance.configuration?.jsonObject("media_attachments")
|
||||
attachmentUploader.addRequest(
|
||||
AttachmentRequest(
|
||||
context = applicationContext,
|
||||
account = account!!,
|
||||
pa = pa,
|
||||
uri = uri,
|
||||
mimeType = mimeType!!,
|
||||
instance = instance,
|
||||
mediaConfig = mediaConfig,
|
||||
serverMaxSqPixel = mediaConfig?.int("image_matrix_limit")?.takeIf { it > 0 },
|
||||
imageResizeConfig = account.getResizeConfig(),
|
||||
maxBytesVideo = min(
|
||||
account.getMovieMaxBytes(instance),
|
||||
mediaConfig?.int("video_size_limit")
|
||||
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
||||
),
|
||||
maxBytesImage = min(
|
||||
account.getImageMaxBytes(instance),
|
||||
mediaConfig?.int("image_size_limit")
|
||||
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
||||
),
|
||||
// onUploadEnd = onUploadEnd
|
||||
attachmentUploader.addRequest(
|
||||
AttachmentRequest(
|
||||
context = applicationContext,
|
||||
account = account,
|
||||
pa = pa,
|
||||
uri = uri,
|
||||
mimeTypeArg = mimeTypeArg,
|
||||
isReply = states.inReplyToId != null,
|
||||
imageResizeConfig = account.getResizeConfig(),
|
||||
maxBytesVideo = { instance, mediaConfig ->
|
||||
min(
|
||||
account.getMovieMaxBytes(instance),
|
||||
mediaConfig?.int("video_size_limit")
|
||||
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ActPost.launchAddAttachmentChannelReader() {
|
||||
launchMain {
|
||||
while (true) {
|
||||
try {
|
||||
val item = addAttachmentChannel.receive()
|
||||
addAttachmentSuspend(item.uri, item.mimeTypeArg)
|
||||
} catch (ex: Throwable) {
|
||||
when (ex) {
|
||||
is CancellationException -> {
|
||||
log.i("launchAddAttachmentChannelReader: cancelled.")
|
||||
break
|
||||
}
|
||||
|
||||
is ClosedChannelException, is ClosedReceiveChannelException -> {
|
||||
log.i("launchAddAttachmentChannelReader: channel closed.")
|
||||
break
|
||||
}
|
||||
|
||||
else ->
|
||||
log.e(ex,"launchAddAttachmentChannelReader: addAttachmentSuspend raise error. retry…")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
maxBytesImage = { instance, mediaConfig ->
|
||||
min(
|
||||
account.getImageMaxBytes(instance),
|
||||
mediaConfig?.int("image_size_limit")
|
||||
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
||||
)
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun ActPost.onPostAttachmentCompleteImpl(pa: PostAttachment) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package jp.juggler.subwaytooter.api.auth
|
||||
|
||||
import android.net.Uri
|
||||
import jp.juggler.subwaytooter.BuildConfig
|
||||
import jp.juggler.subwaytooter.api.SendException
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
|
@ -9,6 +8,7 @@ import jp.juggler.subwaytooter.api.entity.EntityId
|
|||
import jp.juggler.subwaytooter.api.entity.Host
|
||||
import jp.juggler.subwaytooter.api.entity.InstanceType
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
|
@ -23,10 +23,10 @@ class AuthMastodon(override val client: TootApiClient) : AuthBase() {
|
|||
companion object {
|
||||
private val log = LogCategory("MastodonAuth")
|
||||
|
||||
@Suppress("MayBeConstant")
|
||||
@Suppress("MayBeConstant", "RedundantSuppression")
|
||||
val DEBUG_AUTH = false
|
||||
|
||||
const val callbackUrl = "${BuildConfig.customScheme}://oauth/"
|
||||
const val callbackUrl = "${FcmFlavor.CUSTOM_SCHEME}://oauth/"
|
||||
|
||||
fun mastodonScope(ti: TootInstance?) = when {
|
||||
// 古いサーバ
|
||||
|
|
|
@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.api.auth
|
|||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import jp.juggler.subwaytooter.BuildConfig
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
|
@ -11,16 +10,25 @@ import jp.juggler.subwaytooter.api.entity.Host
|
|||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.data.JsonArray
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.buildJsonArray
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.data.digestSHA256
|
||||
import jp.juggler.util.data.encodeHexLower
|
||||
import jp.juggler.util.data.encodeUTF8
|
||||
import jp.juggler.util.data.notBlank
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
||||
class AuthMisskey10(override val client: TootApiClient) : AuthBase() {
|
||||
companion object {
|
||||
private val log = LogCategory("MisskeyOldAuth")
|
||||
private const val callbackUrl = "${BuildConfig.customScheme}://misskey/auth_callback"
|
||||
private const val callbackUrl = "${FcmFlavor.CUSTOM_SCHEME}://misskey/auth_callback"
|
||||
|
||||
fun isCallbackUrl(uriStr: String) =
|
||||
uriStr.startsWith(callbackUrl) ||
|
||||
|
@ -213,7 +221,6 @@ class AuthMisskey10(override val client: TootApiClient) : AuthBase() {
|
|||
linkHelper = LinkHelper.create(ti)
|
||||
)
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||
?.notEmpty() ?: error("missing client id")
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package jp.juggler.subwaytooter.api.auth
|
||||
|
||||
import android.net.Uri
|
||||
import jp.juggler.subwaytooter.BuildConfig
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
|
@ -11,12 +10,13 @@ import jp.juggler.subwaytooter.api.entity.Host
|
|||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* miauth と呼ばれている認証手順。
|
||||
|
@ -26,7 +26,7 @@ class AuthMisskey13(override val client: TootApiClient) : AuthBase() {
|
|||
companion object {
|
||||
private val log = LogCategory("MisskeyMiAuth")
|
||||
private const val appIconUrl = "https://m1j.zzz.ac/subwaytooter-miauth-icon.png"
|
||||
private const val callbackUrl = "${BuildConfig.customScheme}://miauth/auth_callback"
|
||||
private const val callbackUrl = "${FcmFlavor.CUSTOM_SCHEME}://miauth/auth_callback"
|
||||
|
||||
fun isCallbackUrl(uriStr: String) = uriStr.startsWith(callbackUrl)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Intent
|
|||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
|
@ -36,6 +37,8 @@ import jp.juggler.subwaytooter.table.daoSavedAccount
|
|||
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||
import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||
import jp.juggler.subwaytooter.util.openBrowser
|
||||
import jp.juggler.subwaytooter.util.reNotAllowedInUserAgent
|
||||
import jp.juggler.subwaytooter.util.userAgentDefault
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.data.intentOpenDocument
|
||||
|
@ -111,6 +114,7 @@ class AppSettingItem(
|
|||
var onClickEdit: ActAppSetting.() -> Unit = {}
|
||||
var onClickReset: ActAppSetting.() -> Unit = {}
|
||||
var showTextView: ActAppSetting.(TextView) -> Unit = {}
|
||||
var showEditText: ActAppSetting.(EditText) -> Unit = {}
|
||||
|
||||
// for EditText
|
||||
var hint: String? = null
|
||||
|
@ -346,10 +350,10 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
text(PrefS.spClientName, R.string.client_name, InputTypeEx.text)
|
||||
|
||||
text(PrefS.spUserAgent, R.string.user_agent, InputTypeEx.textMultiLine) {
|
||||
hint = App1.userAgentDefault
|
||||
showEditText = { it.hint = userAgentDefault() }
|
||||
filter = { it.replace(ActAppSetting.reLinefeed, " ").trim() }
|
||||
getError = {
|
||||
val m = App1.reNotAllowedInUserAgent.matcher(it)
|
||||
val m = reNotAllowedInUserAgent.matcher(it)
|
||||
when (m.find()) {
|
||||
true -> getString(R.string.user_agent_error, m.group())
|
||||
else -> null
|
||||
|
|
|
@ -17,9 +17,9 @@ import androidx.core.app.NotificationManagerCompat
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.startup.Initializer
|
||||
import androidx.work.ForegroundInfo
|
||||
import jp.juggler.subwaytooter.BuildConfig
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.pref.LazyContextInitializer
|
||||
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
||||
|
@ -56,8 +56,8 @@ enum class NotificationChannels(
|
|||
notificationId = 1,
|
||||
pircTap = 1,
|
||||
pircDelete = 1, // uriでtapとdeleteを区別している
|
||||
uriPrefixDelete = "${BuildConfig.customScheme}://notification_delete/",
|
||||
uriPrefixTap = "${BuildConfig.customScheme}://notification_click/",
|
||||
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://notification_delete/",
|
||||
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://notification_click/",
|
||||
),
|
||||
PullWorker(
|
||||
id = "PollingForegrounder",
|
||||
|
@ -69,8 +69,8 @@ enum class NotificationChannels(
|
|||
notificationId = 2,
|
||||
pircTap = 2,
|
||||
pircDelete = -1,
|
||||
uriPrefixDelete = "${BuildConfig.customScheme}://checker",
|
||||
uriPrefixTap = "${BuildConfig.customScheme}://checker-tap",
|
||||
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://checker",
|
||||
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://checker-tap",
|
||||
),
|
||||
ServerTimeout(
|
||||
id = "ErrorNotification",
|
||||
|
@ -82,8 +82,8 @@ enum class NotificationChannels(
|
|||
notificationId = 3,
|
||||
pircTap = 3,
|
||||
pircDelete = 4,
|
||||
uriPrefixDelete = "${BuildConfig.customScheme}://server-timeout",
|
||||
uriPrefixTap = "${BuildConfig.customScheme}://server-timeout-tap",
|
||||
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://server-timeout",
|
||||
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://server-timeout-tap",
|
||||
),
|
||||
PushMessage(
|
||||
id = "PushMessage",
|
||||
|
@ -95,8 +95,8 @@ enum class NotificationChannels(
|
|||
notificationId = 5,
|
||||
pircTap = 5,
|
||||
pircDelete = 6,
|
||||
uriPrefixDelete = "${BuildConfig.customScheme}://pushMessage",
|
||||
uriPrefixTap = "${BuildConfig.customScheme}://notification_click/",
|
||||
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://pushMessage",
|
||||
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://notification_click/",
|
||||
),
|
||||
Alert(
|
||||
id = "Alert",
|
||||
|
@ -108,8 +108,8 @@ enum class NotificationChannels(
|
|||
notificationId = 7,
|
||||
pircTap = 7,
|
||||
pircDelete = 8,
|
||||
uriPrefixDelete = "${BuildConfig.customScheme}://alert",
|
||||
uriPrefixTap = "${BuildConfig.customScheme}://alert-tap",
|
||||
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://alert",
|
||||
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://alert-tap",
|
||||
),
|
||||
PushWorker(
|
||||
id = "PushMessageWorker",
|
||||
|
@ -121,8 +121,8 @@ enum class NotificationChannels(
|
|||
notificationId = 9,
|
||||
pircTap = 9,
|
||||
pircDelete = 10,
|
||||
uriPrefixDelete = "${BuildConfig.customScheme}://pushWorker",
|
||||
uriPrefixTap = "${BuildConfig.customScheme}://pushWorker-tag",
|
||||
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://pushWorker",
|
||||
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://pushWorker-tag",
|
||||
),
|
||||
|
||||
;
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.startup.Initializer
|
||||
import jp.juggler.subwaytooter.BuildConfig
|
||||
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||
import jp.juggler.util.os.applicationContextSafe
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
|
@ -21,7 +21,7 @@ val lazyPref
|
|||
?: LazyContextHolder.prefNullable
|
||||
?: error("LazyContextHolder not initialized")
|
||||
|
||||
const val FILE_PROVIDER_AUTHORITY = "${BuildConfig.APPLICATION_ID}.FileProvider"
|
||||
const val FILE_PROVIDER_AUTHORITY = "${FcmFlavor.APPLICATION_ID}.FileProvider"
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object LazyContextHolder {
|
||||
|
|
|
@ -7,11 +7,14 @@ import android.os.Build
|
|||
import jp.juggler.media.generateTempFile
|
||||
import jp.juggler.media.transcodeAudio
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.TootApiCallback
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.getStreamSize
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.errorEx
|
||||
import jp.juggler.util.media.ResizeConfig
|
||||
|
@ -20,6 +23,7 @@ import jp.juggler.util.media.createResizedBitmap
|
|||
import jp.juggler.util.media.transcodeVideo
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.concurrent.CancellationException
|
||||
import kotlin.math.min
|
||||
|
||||
class AttachmentRequest(
|
||||
|
@ -27,13 +31,11 @@ class AttachmentRequest(
|
|||
val account: SavedAccount,
|
||||
val pa: PostAttachment,
|
||||
val uri: Uri,
|
||||
var mimeType: String,
|
||||
var mimeTypeArg: String?,
|
||||
val imageResizeConfig: ResizeConfig,
|
||||
val serverMaxSqPixel: Int?,
|
||||
val instance: TootInstance,
|
||||
val mediaConfig: JsonObject?,
|
||||
val maxBytesVideo: Int,
|
||||
val maxBytesImage: Int,
|
||||
val maxBytesVideo: (instance: TootInstance, mediaConfig: JsonObject?) -> Int,
|
||||
val maxBytesImage: (instance: TootInstance, mediaConfig: JsonObject?) -> Int,
|
||||
val isReply: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
private val log = LogCategory("AttachmentRequest")
|
||||
|
@ -51,14 +53,49 @@ class AttachmentRequest(
|
|||
"audio/x-wav",
|
||||
"audio/3gpp",
|
||||
)
|
||||
// val badAudioType = setOf(
|
||||
|
||||
// val badAudioType = setOf(
|
||||
// "audio/mpeg","audio/aac",
|
||||
// "audio/m4a","audio/x-m4a","audio/mp4",
|
||||
// "video/x-ms-asf",
|
||||
// )
|
||||
private suspend fun Context.getInstance(account: SavedAccount): TootInstance {
|
||||
val client = TootApiClient(
|
||||
context = this,
|
||||
callback = object : TootApiCallback {
|
||||
override suspend fun isApiCancelled() = false
|
||||
}
|
||||
).apply {
|
||||
this.account = account
|
||||
}
|
||||
val (instance, ri) = TootInstance.get(client = client)
|
||||
if (instance != null) return instance
|
||||
when (ri) {
|
||||
null -> throw CancellationException()
|
||||
else -> error("missing instance information. ${ri.error}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var _instance: TootInstance? = null
|
||||
|
||||
suspend fun instance(): TootInstance {
|
||||
_instance?.let { return it }
|
||||
return context.getInstance(account).also { _instance = it }
|
||||
}
|
||||
|
||||
suspend fun mediaConfig(): JsonObject? =
|
||||
instance().configuration?.jsonObject("media_attachments")
|
||||
|
||||
private suspend fun serverMaxSqPixel(): Int? =
|
||||
mediaConfig()?.int("image_matrix_limit")?.takeIf { it > 0 }
|
||||
|
||||
val mimeType
|
||||
get() = uri.resolveMimeType(mimeTypeArg, context)?.notEmpty()
|
||||
?: error(context.getString(R.string.mime_type_missing))
|
||||
|
||||
suspend fun createOpener(): InputStreamOpener {
|
||||
val mimeType = this.mimeType
|
||||
|
||||
// GIFはそのまま投げる
|
||||
if (mimeType == MIME_TYPE_GIF) {
|
||||
|
@ -128,10 +165,12 @@ class AttachmentRequest(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createResizedImageOpener(): InputStreamOpener {
|
||||
private suspend fun createResizedImageOpener(): InputStreamOpener {
|
||||
try {
|
||||
pa.progress = context.getString(R.string.attachment_handling_compress)
|
||||
|
||||
val instance = instance()
|
||||
|
||||
val canUseWebP = try {
|
||||
PrefB.bpUseWebP.value && MIME_TYPE_WEBP.mimeTypeIsSupported(instance)
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -154,7 +193,7 @@ class AttachmentRequest(
|
|||
uri,
|
||||
imageResizeConfig,
|
||||
canSkip = canUseOriginal,
|
||||
serverMaxSqPixel = serverMaxSqPixel
|
||||
serverMaxSqPixel = serverMaxSqPixel()
|
||||
)?.let { bitmap ->
|
||||
try {
|
||||
return bitmap.compressAutoType(canUseWebP)
|
||||
|
@ -273,14 +312,16 @@ class AttachmentRequest(
|
|||
val tempFile = File(cacheDir, "movie." + Thread.currentThread().id + ".tmp")
|
||||
val outFile = File(cacheDir, "movie." + Thread.currentThread().id + ".mp4")
|
||||
var resultFile: File? = null
|
||||
|
||||
try {
|
||||
|
||||
// 入力ファイルをコピーする
|
||||
(context.contentResolver.openInputStream(uri)
|
||||
?: error("openInputStream returns null.")).use { inStream ->
|
||||
FileOutputStream(tempFile).use { inStream.copyTo(it) }
|
||||
}
|
||||
|
||||
val mediaConfig = mediaConfig()
|
||||
|
||||
// 動画のメタデータを調べる
|
||||
val info = tempFile.videoInfo
|
||||
|
||||
|
@ -330,11 +371,13 @@ class AttachmentRequest(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun createResizedAudioOpener(srcBytes: Long): InputStreamOpener =
|
||||
when {
|
||||
private suspend fun createResizedAudioOpener(srcBytes: Long): InputStreamOpener {
|
||||
val instance = instance()
|
||||
val mediaConfig = mediaConfig()
|
||||
return when {
|
||||
mimeType.mimeTypeIsSupported(instance) &&
|
||||
goodAudioType.contains(mimeType) &&
|
||||
srcBytes <= maxBytesVideo -> contentUriOpener(
|
||||
srcBytes <= maxBytesVideo(instance,mediaConfig).toLong() -> contentUriOpener(
|
||||
context.contentResolver,
|
||||
uri,
|
||||
mimeType,
|
||||
|
@ -360,4 +403,6 @@ class AttachmentRequest(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import jp.juggler.subwaytooter.api.TootApiClient
|
|||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.auth.AuthBase
|
||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||
import jp.juggler.subwaytooter.api.entity.InstanceType
|
||||
import jp.juggler.subwaytooter.api.entity.ServiceType
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachment
|
||||
|
@ -35,13 +36,14 @@ import jp.juggler.util.network.toPost
|
|||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.network.toPut
|
||||
import jp.juggler.util.network.toPutRequestBuilder
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MultipartBody
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
class AttachmentUploader(
|
||||
|
@ -55,130 +57,137 @@ class AttachmentUploader(
|
|||
private val safeContext = activity.applicationContext!!
|
||||
private var lastAttachmentAdd = 0L
|
||||
private var lastAttachmentComplete = 0L
|
||||
private var channel: Channel<AttachmentRequest>? = null
|
||||
private val channel = Channel<AttachmentRequest>(capacity = Channel.UNLIMITED)
|
||||
|
||||
private fun prepareChannel(): Channel<AttachmentRequest> {
|
||||
// double check before/after lock
|
||||
channel?.let { return it }
|
||||
synchronized(this) {
|
||||
channel?.let { return it }
|
||||
return Channel<AttachmentRequest>(capacity = Channel.UNLIMITED)
|
||||
.also {
|
||||
channel = it
|
||||
launchIO {
|
||||
while (true) {
|
||||
val request = try {
|
||||
it.receive()
|
||||
} catch (ex: Throwable) {
|
||||
when (ex) {
|
||||
is CancellationException, is ClosedReceiveChannelException -> break
|
||||
else -> {
|
||||
safeContext.showToast(ex)
|
||||
continue
|
||||
}
|
||||
}
|
||||
init {
|
||||
launchIO {
|
||||
while (true) {
|
||||
try {
|
||||
val request = channel.receive()
|
||||
if (request.pa.isCancelled) continue
|
||||
withContext(AppDispatchers.MainImmediate) {
|
||||
val pa = request.pa
|
||||
pa.status = try {
|
||||
withContext(pa.job + AppDispatchers.IO) {
|
||||
request.upload()
|
||||
}
|
||||
val result = try {
|
||||
if (request.pa.isCancelled) continue
|
||||
withContext(request.pa.job + AppDispatchers.IO) {
|
||||
request.upload()
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
TootApiResult(ex.withCaption("upload failed."))
|
||||
request.pa.progress = ""
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastAttachmentComplete >= 5000L) {
|
||||
safeContext.showToast(false, R.string.attachment_uploaded)
|
||||
}
|
||||
try {
|
||||
request.pa.progress = ""
|
||||
withContext(AppDispatchers.MainImmediate) {
|
||||
handleResult(request, result)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
when (ex) {
|
||||
is CancellationException, is ClosedReceiveChannelException -> break
|
||||
else -> {
|
||||
safeContext.showToast(ex)
|
||||
continue
|
||||
}
|
||||
}
|
||||
lastAttachmentComplete = now
|
||||
|
||||
PostAttachment.Status.Ok
|
||||
} catch (ex: Throwable) {
|
||||
if (ex is CancellationException) {
|
||||
// キャンセルはメッセージを出さない
|
||||
} else if (ex.message?.contains("cancel", ignoreCase = true) == true) {
|
||||
// キャンセルはメッセージを出さない
|
||||
} else if (ex is IllegalStateException) {
|
||||
safeContext.showToast(true, "${ex.message}")
|
||||
} else {
|
||||
safeContext.showToast(true, ex.withCaption("upload failed."))
|
||||
}
|
||||
PostAttachment.Status.Error
|
||||
}
|
||||
// 投稿中に画面回転があった場合、新しい画面のコールバックを呼び出す必要がある
|
||||
pa.callback?.onPostAttachmentComplete(pa)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
when (ex) {
|
||||
is CancellationException -> {
|
||||
log.i("AttachmentUploader: channel cancelled.")
|
||||
break
|
||||
}
|
||||
|
||||
is ClosedChannelException, is ClosedReceiveChannelException -> {
|
||||
log.i("AttachmentUploader: channel closed.")
|
||||
break
|
||||
}
|
||||
|
||||
else -> safeContext.showToast(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onActivityDestroy() {
|
||||
try {
|
||||
synchronized(this) {
|
||||
channel?.close()
|
||||
channel = null
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "can't close channel.")
|
||||
channel.close()
|
||||
} catch (ignored: Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
fun addRequest(request: AttachmentRequest) {
|
||||
|
||||
request.pa.progress = safeContext.getString(R.string.attachment_handling_start)
|
||||
|
||||
// アップロード開始トースト(連発しない)
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastAttachmentAdd >= 5000L) {
|
||||
safeContext.showToast(false, R.string.attachment_uploading)
|
||||
}
|
||||
lastAttachmentAdd = now
|
||||
|
||||
// マストドンは添付メディアをID順に表示するため
|
||||
// 画像が複数ある場合は一つずつ処理する必要がある
|
||||
// 投稿画面ごとに1スレッドだけ作成してバックグラウンド処理を行う
|
||||
launchIO { prepareChannel().send(request) }
|
||||
// 投稿画面ごとに作成したチャネルにsendして、受け側は順次処理する
|
||||
launchIO {
|
||||
try {
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
// アップロード開始トースト(連発しない)
|
||||
if (now - lastAttachmentAdd >= 5000L) {
|
||||
safeContext.showToast(false, R.string.attachment_uploading)
|
||||
}
|
||||
|
||||
lastAttachmentAdd = now
|
||||
channel.send(request)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "addRequest failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private suspend fun AttachmentRequest.upload(): TootApiResult? {
|
||||
private suspend fun AttachmentRequest.upload() {
|
||||
val account = this.account
|
||||
val instance = this.instance()
|
||||
val mediaConfig = this.mediaConfig()
|
||||
|
||||
// ensure mimeType
|
||||
this.mimeType
|
||||
|
||||
if (instance.instanceType == InstanceType.Pixelfed && isReply) {
|
||||
error(safeContext.getString(R.string.pixelfed_does_not_allow_reply_with_media))
|
||||
}
|
||||
|
||||
val client = TootApiClient(safeContext, callback = object : TootApiCallback {
|
||||
override suspend fun isApiCancelled() = !coroutineContext.isActive
|
||||
})
|
||||
|
||||
client.account = account
|
||||
client.currentCallCallback = {}
|
||||
|
||||
// 入力データの変換など
|
||||
val opener = this.createOpener()
|
||||
try {
|
||||
if (mimeType.isEmpty()) return TootApiResult("mime_type is empty.")
|
||||
|
||||
val client = TootApiClient(safeContext, callback = object : TootApiCallback {
|
||||
override suspend fun isApiCancelled() = !coroutineContext.isActive
|
||||
})
|
||||
|
||||
client.account = account
|
||||
client.currentCallCallback = {}
|
||||
|
||||
val (ti, tiResult) = TootInstance.get(client)
|
||||
ti ?: return tiResult
|
||||
|
||||
// 入力データの変換など
|
||||
val opener = this.createOpener()
|
||||
val maxBytes = when (opener.isImage) {
|
||||
true -> maxBytesImage
|
||||
else -> maxBytesVideo
|
||||
true -> maxBytesImage(instance, mediaConfig)
|
||||
else -> maxBytesVideo(instance, mediaConfig)
|
||||
}
|
||||
if (opener.contentLength > maxBytes) {
|
||||
return TootApiResult(
|
||||
safeContext.getString(R.string.file_size_too_big, maxBytes / 1_000_000)
|
||||
)
|
||||
}
|
||||
|
||||
if (!opener.mimeType.mimeTypeIsSupported(instance)) {
|
||||
return TootApiResult(
|
||||
safeContext.getString(R.string.mime_type_not_acceptable, opener.mimeType)
|
||||
)
|
||||
if (opener.contentLength > maxBytes.toLong()) {
|
||||
error(safeContext.getString(R.string.file_size_too_big, maxBytes / 1_000_000))
|
||||
} else if (!opener.mimeType.mimeTypeIsSupported(instance)) {
|
||||
error(safeContext.getString(R.string.mime_type_not_acceptable, opener.mimeType))
|
||||
}
|
||||
|
||||
val fileName = fixDocumentName(getDocumentName(safeContext.contentResolver, uri))
|
||||
pa.progress = safeContext.getString(R.string.attachment_handling_uploading, 0)
|
||||
fun writeProgress(percent: Int) {
|
||||
if (percent < 100) {
|
||||
pa.progress =
|
||||
safeContext.getString(R.string.attachment_handling_uploading, percent)
|
||||
pa.progress = if (percent < 100) {
|
||||
safeContext.getString(R.string.attachment_handling_uploading, percent)
|
||||
} else {
|
||||
pa.progress = safeContext.getString(R.string.attachment_handling_waiting)
|
||||
safeContext.getString(R.string.attachment_handling_waiting)
|
||||
}
|
||||
}
|
||||
|
||||
return if (account.isMisskey) {
|
||||
if (account.isMisskey) {
|
||||
val multipartBuilder = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
|
||||
|
@ -199,16 +208,13 @@ class AttachmentUploader(
|
|||
)
|
||||
opener.deleteTempFile()
|
||||
|
||||
val jsonObject = result?.jsonObject
|
||||
if (jsonObject != null) {
|
||||
val a = parseItem(jsonObject) { tootAttachment(ServiceType.MISSKEY, it) }
|
||||
if (a == null) {
|
||||
result.error = "TootAttachment.parse failed"
|
||||
} else {
|
||||
pa.attachment = a
|
||||
}
|
||||
}
|
||||
result
|
||||
result ?: throw CancellationException()
|
||||
|
||||
val jsonObject = result.jsonObject
|
||||
?: error(result.error ?: "missing error detail")
|
||||
pa.attachment =
|
||||
parseItem(jsonObject) { tootAttachment(ServiceType.MISSKEY, it) }
|
||||
?: error("TootAttachment.parse failed")
|
||||
} else {
|
||||
suspend fun postMedia(path: String) = client.request(
|
||||
path,
|
||||
|
@ -226,13 +232,14 @@ class AttachmentUploader(
|
|||
|
||||
suspend fun postV2(): TootApiResult? {
|
||||
// 3.1.3未満は v1 APIを使う
|
||||
if (!ti.versionGE(TootInstance.VERSION_3_1_3)) {
|
||||
if (!instance.versionGE(TootInstance.VERSION_3_1_3)) {
|
||||
return postV1()
|
||||
}
|
||||
|
||||
// v2 APIを試す
|
||||
val result = postMedia("/api/v2/media")
|
||||
val code = result?.response?.code // complete,or 4xx error
|
||||
?: throw CancellationException()
|
||||
val code = result.response?.code // complete,or 4xx error
|
||||
when {
|
||||
// 404ならv1 APIにフォールバック
|
||||
code == 404 -> return postV1()
|
||||
|
@ -242,18 +249,17 @@ class AttachmentUploader(
|
|||
|
||||
// ポーリングして処理完了を待つ
|
||||
pa.progress = safeContext.getString(R.string.attachment_handling_waiting_async)
|
||||
val id = parseItem(result?.jsonObject) {
|
||||
|
||||
val id = parseItem(result.jsonObject) {
|
||||
tootAttachment(ServiceType.MASTODON, it)
|
||||
}?.id
|
||||
?: return TootApiResult("/api/v2/media did not return the media ID.")
|
||||
}?.id ?: error("/api/v2/media did not return the media ID.")
|
||||
|
||||
var lastResponse = SystemClock.elapsedRealtime()
|
||||
loop@ while (true) {
|
||||
|
||||
while (true) {
|
||||
delay(1000L)
|
||||
|
||||
val r2 = client.request("/api/v1/media/$id")
|
||||
?: return null // cancelled
|
||||
?: throw CancellationException()
|
||||
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
when (r2.response?.code) {
|
||||
|
@ -264,29 +270,22 @@ class AttachmentUploader(
|
|||
206 -> lastResponse = now
|
||||
|
||||
// temporary errors, check timeout without 206 response.
|
||||
else -> if (now - lastResponse >= 120000L) {
|
||||
return TootApiResult("timeout.")
|
||||
}
|
||||
else -> if (now - lastResponse >= 120000L) error("timeout.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = postV2()
|
||||
opener.deleteTempFile()
|
||||
?: throw CancellationException()
|
||||
|
||||
val jsonObject = result?.jsonObject
|
||||
if (jsonObject != null) {
|
||||
when (val a = parseItem(jsonObject) {
|
||||
tootAttachment(ServiceType.MASTODON, it)
|
||||
}) {
|
||||
null -> result.error = "TootAttachment.parse failed"
|
||||
else -> pa.attachment = a
|
||||
}
|
||||
}
|
||||
result
|
||||
val jsonObject = result.jsonObject
|
||||
?: error(result.error ?: "missing error detail")
|
||||
|
||||
pa.attachment = parseItem(jsonObject) { tootAttachment(ServiceType.MASTODON, it) }
|
||||
?: error("TootAttachment.parse failed")
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
return TootApiResult(ex.withCaption("read failed."))
|
||||
} finally {
|
||||
opener.deleteTempFile()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,38 +305,6 @@ class AttachmentUploader(
|
|||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun handleResult(request: AttachmentRequest, result: TootApiResult?) {
|
||||
val pa = request.pa
|
||||
pa.status = when (pa.attachment) {
|
||||
null -> {
|
||||
if (result != null) {
|
||||
when {
|
||||
// キャンセルはトーストを出さない
|
||||
result.error?.contains("cancel", ignoreCase = true) == true -> Unit
|
||||
else -> safeContext.showToast(
|
||||
true,
|
||||
"${result.error} ${result.response?.request?.method} ${result.response?.request?.url}"
|
||||
)
|
||||
}
|
||||
}
|
||||
PostAttachment.Status.Error
|
||||
}
|
||||
|
||||
else -> {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastAttachmentComplete >= 5000L) {
|
||||
safeContext.showToast(false, R.string.attachment_uploaded)
|
||||
}
|
||||
lastAttachmentComplete = now
|
||||
|
||||
PostAttachment.Status.Ok
|
||||
}
|
||||
}
|
||||
|
||||
// 投稿中に画面回転があった場合、新しい画面のコールバックを呼び出す必要がある
|
||||
pa.callback?.onPostAttachmentComplete(pa)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// 添付データのカスタムサムネイル
|
||||
suspend fun uploadCustomThumbnail(
|
||||
|
@ -346,67 +313,62 @@ class AttachmentUploader(
|
|||
pa: PostAttachment,
|
||||
): TootApiResult? = try {
|
||||
safeContext.runApiTask(account) { client ->
|
||||
val (ti, ri) = TootInstance.get(client)
|
||||
ti ?: return@runApiTask ri
|
||||
|
||||
val mimeType = src.uri.resolveMimeType(src.mimeType, safeContext)
|
||||
if (mimeType.isNullOrEmpty()) {
|
||||
return@runApiTask TootApiResult(safeContext.getString(R.string.mime_type_missing))
|
||||
}
|
||||
|
||||
val mediaConfig = ti.configuration?.jsonObject("media_attachments")
|
||||
val ar = AttachmentRequest(
|
||||
context = safeContext,
|
||||
account = account,
|
||||
pa = pa,
|
||||
uri = src.uri,
|
||||
mimeType = mimeType,
|
||||
instance = ti,
|
||||
mediaConfig = mediaConfig,
|
||||
mimeTypeArg = src.mimeType,
|
||||
imageResizeConfig = ResizeConfig(ResizeType.SquarePixel, 400),
|
||||
serverMaxSqPixel = mediaConfig?.int("image_matrix_limit")?.takeIf { it > 0 },
|
||||
maxBytesImage = 1000000,
|
||||
maxBytesVideo = 1000000,
|
||||
maxBytesImage = { _, _ -> 1000000 },
|
||||
maxBytesVideo = { _, _ -> 1000000 },
|
||||
)
|
||||
val instance = ar.instance()
|
||||
val mediaConfig = ar.mediaConfig()
|
||||
val maxBytesImage = ar.maxBytesImage(instance, mediaConfig)
|
||||
|
||||
val opener = ar.createOpener()
|
||||
if (opener.contentLength > ar.maxBytesImage) {
|
||||
return@runApiTask TootApiResult(
|
||||
getString(
|
||||
R.string.file_size_too_big,
|
||||
ar.maxBytesImage / 1000000
|
||||
)
|
||||
)
|
||||
}
|
||||
try{
|
||||
|
||||
val fileName = fixDocumentName(getDocumentName(safeContext.contentResolver, src.uri))
|
||||
|
||||
if (account.isMisskey) {
|
||||
opener.deleteTempFile()
|
||||
TootApiResult("custom thumbnail is not supported on misskey account.")
|
||||
} else {
|
||||
val result = client.request(
|
||||
"/api/v1/media/${pa.attachment?.id}",
|
||||
MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart(
|
||||
"thumbnail",
|
||||
fileName,
|
||||
opener.toRequestBody(),
|
||||
if (opener.contentLength > maxBytesImage.toLong()) {
|
||||
return@runApiTask TootApiResult(
|
||||
getString(
|
||||
R.string.file_size_too_big,
|
||||
maxBytesImage / 1000000
|
||||
)
|
||||
.build().toPut()
|
||||
)
|
||||
opener.deleteTempFile()
|
||||
|
||||
val jsonObject = result?.jsonObject
|
||||
if (jsonObject != null) {
|
||||
val a = parseItem(jsonObject) { tootAttachment(ServiceType.MASTODON, it) }
|
||||
if (a == null) {
|
||||
result.error = "TootAttachment.parse failed"
|
||||
} else {
|
||||
pa.attachment = a
|
||||
}
|
||||
)
|
||||
}
|
||||
result
|
||||
|
||||
val fileName = fixDocumentName(getDocumentName(safeContext.contentResolver, src.uri))
|
||||
|
||||
if (account.isMisskey) {
|
||||
TootApiResult("custom thumbnail is not supported on misskey account.")
|
||||
} else {
|
||||
val result = client.request(
|
||||
"/api/v1/media/${pa.attachment?.id}",
|
||||
MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart(
|
||||
"thumbnail",
|
||||
fileName,
|
||||
opener.toRequestBody(),
|
||||
)
|
||||
.build().toPut()
|
||||
)
|
||||
|
||||
val jsonObject = result?.jsonObject
|
||||
if (jsonObject != null) {
|
||||
val a = parseItem(jsonObject) { tootAttachment(ServiceType.MASTODON, it) }
|
||||
if (a == null) {
|
||||
result.error = "TootAttachment.parse failed"
|
||||
} else {
|
||||
pa.attachment = a
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}finally{
|
||||
opener.deleteTempFile()
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package jp.juggler.subwaytooter.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import jp.juggler.subwaytooter.App1
|
||||
import jp.juggler.subwaytooter.pref.PrefS
|
||||
import jp.juggler.util.data.asciiPattern
|
||||
import jp.juggler.util.getPackageInfoCompat
|
||||
|
||||
val reNotAllowedInUserAgent = "[^\\x21-\\x7e]+".asciiPattern()
|
||||
|
||||
fun Context.userAgentDefault(): String {
|
||||
val versionName = try {
|
||||
packageManager.getPackageInfoCompat(packageName)!!.versionName
|
||||
} catch (ex: Throwable) {
|
||||
App1.log.e(ex, "can't get versionName.")
|
||||
"0.0.0"
|
||||
}
|
||||
return "SubwayTooter/${versionName} Android/${Build.VERSION.RELEASE}"
|
||||
}
|
||||
|
||||
fun Context.getUserAgent(): String {
|
||||
val userAgentCustom = PrefS.spUserAgent.value
|
||||
return when {
|
||||
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
|
||||
.find() -> userAgentCustom
|
||||
|
||||
else -> userAgentDefault()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package jp.juggler.subwaytooter.push
|
||||
|
||||
object FcmFlavor {
|
||||
const val APPLICATION_ID = "jp.juggler.subwaytooter.noFcm"
|
||||
const val CUSTOM_SCHEME = "subwaytooternofcm"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
object ReleaseType {
|
||||
const val isDebug = false
|
||||
const val isRelease = !isDebug
|
||||
}
|
|
@ -5,9 +5,11 @@ import android.net.Uri
|
|||
import android.os.Looper
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.transformer.TransformationException
|
||||
import androidx.media3.transformer.Composition
|
||||
import androidx.media3.transformer.EditedMediaItem
|
||||
import androidx.media3.transformer.ExportException
|
||||
import androidx.media3.transformer.ExportResult
|
||||
import androidx.media3.transformer.TransformationRequest
|
||||
import androidx.media3.transformer.TransformationResult
|
||||
import androidx.media3.transformer.Transformer
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -57,35 +59,39 @@ suspend fun transcodeAudio(
|
|||
val tmpFile = context.generateTempFile("transcodeAudio")
|
||||
|
||||
// Transformerは単一スレッドで処理する要件
|
||||
val result: TransformationResult = withContext(Dispatchers.Main.immediate) {
|
||||
val result: ExportResult = withContext(Dispatchers.Main.immediate) {
|
||||
val looper = Looper.getMainLooper()
|
||||
suspendCancellableCoroutine { cont ->
|
||||
val transformerListener = object : Transformer.Listener {
|
||||
override fun onTransformationCompleted(
|
||||
inputMediaItem: MediaItem,
|
||||
transformationResult: TransformationResult,
|
||||
override fun onCompleted(
|
||||
composition: Composition,
|
||||
exportResult: ExportResult,
|
||||
) {
|
||||
log.i("onTransformationCompleted inputMediaItem=$inputMediaItem transformationResult=$transformationResult")
|
||||
if (cont.isActive) cont.resume(transformationResult) {}
|
||||
val mediaItem = composition.sequences[0].editedMediaItems[0].mediaItem
|
||||
log.i("onCompleted mediaItem=$mediaItem exportResult=$exportResult")
|
||||
if (cont.isActive) cont.resume(exportResult) {}
|
||||
}
|
||||
|
||||
override fun onTransformationError(
|
||||
inputMediaItem: MediaItem,
|
||||
exception: TransformationException,
|
||||
override fun onError(
|
||||
composition: Composition,
|
||||
exportResult: ExportResult,
|
||||
exportException: ExportException,
|
||||
) {
|
||||
val mediaItem = composition.sequences[0].editedMediaItems[0].mediaItem
|
||||
log.e(
|
||||
exception,
|
||||
"onTransformationError inputMediaItem=$inputMediaItem"
|
||||
exportException,
|
||||
"onError inputMediaItem=$mediaItem, exportResult=$exportResult"
|
||||
)
|
||||
if (cont.isActive) cont.resumeWithException(exception)
|
||||
if (cont.isActive) cont.resumeWithException(exportException)
|
||||
}
|
||||
|
||||
override fun onFallbackApplied(
|
||||
inputMediaItem: MediaItem,
|
||||
composition: Composition,
|
||||
originalTransformationRequest: TransformationRequest,
|
||||
fallbackTransformationRequest: TransformationRequest,
|
||||
) {
|
||||
log.i("onFallbackApplied inputMediaItem=$inputMediaItem original=$originalTransformationRequest fallback=$fallbackTransformationRequest")
|
||||
val mediaItem = composition.sequences[0].editedMediaItems[0].mediaItem
|
||||
log.i("onFallbackApplied mediaItem=$mediaItem original=$originalTransformationRequest fallback=$fallbackTransformationRequest")
|
||||
}
|
||||
}
|
||||
val transformer = Transformer.Builder(context)
|
||||
|
@ -95,11 +101,13 @@ suspend fun transcodeAudio(
|
|||
.setAudioMimeType(encodeMimeType)
|
||||
.build()
|
||||
)
|
||||
.setRemoveVideo(true)
|
||||
.setRemoveAudio(false)
|
||||
.addListener(transformerListener)
|
||||
.build()
|
||||
transformer.startTransformation(inputMediaItem, tmpFile.canonicalPath)
|
||||
|
||||
val editedMediaItem = EditedMediaItem.Builder(inputMediaItem).apply {
|
||||
setRemoveVideo(true)
|
||||
}.build()
|
||||
transformer.start(editedMediaItem, tmpFile.canonicalPath)
|
||||
cont.invokeOnCancellation {
|
||||
transformer.cancel()
|
||||
}
|
||||
|
|
|
@ -32,5 +32,3 @@ org.gradle.vfs.watch=true
|
|||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.debug.obsoleteApi=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.enableBuildConfigAsBytecode=true
|
||||
|
|
Loading…
Reference in New Issue