2021-06-06 18:06:01 +00:00
|
|
|
package com.lagradost.cloudstream3.ui
|
|
|
|
|
2021-07-02 18:46:18 +00:00
|
|
|
import android.graphics.Color
|
2021-06-06 18:06:01 +00:00
|
|
|
import android.os.Bundle
|
2021-07-02 18:46:18 +00:00
|
|
|
import android.util.Log
|
2021-06-06 18:06:01 +00:00
|
|
|
import android.view.Menu
|
2021-06-14 19:24:07 +00:00
|
|
|
import android.view.View.*
|
2021-07-02 18:46:18 +00:00
|
|
|
import android.widget.*
|
|
|
|
import androidx.appcompat.app.AlertDialog
|
|
|
|
import androidx.core.graphics.toColorInt
|
2021-06-14 00:00:29 +00:00
|
|
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
|
|
import com.fasterxml.jackson.databind.json.JsonMapper
|
|
|
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
2021-06-14 16:58:43 +00:00
|
|
|
import com.google.android.gms.cast.MediaQueueItem
|
|
|
|
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
|
2021-07-02 18:46:18 +00:00
|
|
|
import com.google.android.gms.cast.MediaTrack
|
|
|
|
import com.google.android.gms.cast.TextTrackStyle
|
|
|
|
import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_OUTLINE
|
2021-06-06 18:06:01 +00:00
|
|
|
import com.google.android.gms.cast.framework.CastButtonFactory
|
|
|
|
import com.google.android.gms.cast.framework.CastSession
|
2021-06-14 19:24:07 +00:00
|
|
|
import com.google.android.gms.cast.framework.media.RemoteMediaClient
|
2021-06-06 18:06:01 +00:00
|
|
|
import com.google.android.gms.cast.framework.media.uicontroller.UIController
|
|
|
|
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
|
2021-06-14 16:58:43 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
2021-06-06 18:06:01 +00:00
|
|
|
import com.lagradost.cloudstream3.R
|
2021-07-02 18:46:18 +00:00
|
|
|
import com.lagradost.cloudstream3.SubtitleFile
|
2021-06-14 16:58:43 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.Resource
|
|
|
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
|
|
|
import com.lagradost.cloudstream3.sortUrls
|
|
|
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
2021-06-14 19:24:07 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.CastHelper.awaitLinks
|
2021-06-14 16:58:43 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.CastHelper.getMediaInfo
|
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
|
|
|
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
|
|
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.withContext
|
2021-06-06 18:06:01 +00:00
|
|
|
import org.json.JSONObject
|
|
|
|
|
|
|
|
class SkipOpController(val view: ImageView) : UIController() {
|
|
|
|
init {
|
|
|
|
view.setImageResource(R.drawable.exo_controls_fastforward)
|
|
|
|
view.setOnClickListener {
|
|
|
|
remoteMediaClient.seek(remoteMediaClient.approximateStreamPosition + 85000)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
private fun RemoteMediaClient.getItemIndex(): Int? {
|
|
|
|
return try {
|
|
|
|
val index = this.mediaQueue?.itemIds?.indexOf(this.currentItem?.itemId ?: 0)
|
|
|
|
if (index == null || index < 0) null else index
|
|
|
|
} catch (e: Exception) {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SkipNextEpisodeController(val view: ImageView) : UIController() {
|
|
|
|
init {
|
2021-06-15 16:07:20 +00:00
|
|
|
view.setImageResource(R.drawable.ic_baseline_skip_next_24)
|
2021-06-14 19:24:07 +00:00
|
|
|
view.setOnClickListener {
|
|
|
|
remoteMediaClient?.let {
|
|
|
|
it.queueNext(JSONObject())
|
|
|
|
view.visibility = GONE // TO PREVENT MULTI CLICK
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onMediaStatusUpdated() {
|
|
|
|
super.onMediaStatusUpdated()
|
|
|
|
view.visibility = GONE
|
|
|
|
val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return
|
|
|
|
val itemCount = remoteMediaClient?.mediaQueue?.itemCount ?: return
|
|
|
|
if (itemCount - currentIdIndex > 1 && remoteMediaClient?.isLoadingNextItem == false) {
|
|
|
|
view.visibility = VISIBLE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 16:58:43 +00:00
|
|
|
data class MetadataHolder(
|
|
|
|
val apiName: String,
|
2021-06-16 22:31:41 +00:00
|
|
|
val isMovie: Boolean,
|
2021-06-14 16:58:43 +00:00
|
|
|
val title: String?,
|
|
|
|
val poster: String?,
|
|
|
|
val currentEpisodeIndex: Int,
|
|
|
|
val episodes: List<ResultEpisode>,
|
|
|
|
val currentLinks: List<ExtractorLink>,
|
2021-07-02 18:46:18 +00:00
|
|
|
val currentSubtitles: List<SubtitleFile>
|
2021-06-14 16:58:43 +00:00
|
|
|
)
|
2021-06-14 00:00:29 +00:00
|
|
|
|
|
|
|
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() {
|
|
|
|
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
|
|
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
2021-06-06 18:06:01 +00:00
|
|
|
|
|
|
|
init {
|
|
|
|
view.setImageResource(R.drawable.ic_baseline_playlist_play_24)
|
|
|
|
view.setOnClickListener {
|
2021-06-16 22:31:41 +00:00
|
|
|
// lateinit var dialog: AlertDialog
|
2021-06-14 00:00:29 +00:00
|
|
|
val holder = getCurrentMetaData()
|
|
|
|
|
2021-07-02 18:46:18 +00:00
|
|
|
|
2021-06-14 00:00:29 +00:00
|
|
|
if (holder != null) {
|
2021-06-14 16:58:43 +00:00
|
|
|
val items = holder.currentLinks
|
2021-06-14 19:24:07 +00:00
|
|
|
if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) {
|
2021-07-02 18:46:18 +00:00
|
|
|
val subTracks =
|
|
|
|
remoteMediaClient?.mediaInfo?.mediaTracks?.filter { it.type == MediaTrack.TYPE_TEXT }
|
|
|
|
?: ArrayList()
|
|
|
|
|
|
|
|
val bottomSheetDialogBuilder = AlertDialog.Builder(view.context, R.style.AlertDialogCustomBlack)
|
|
|
|
bottomSheetDialogBuilder.setView(R.layout.sort_bottom_sheet)
|
|
|
|
val bottomSheetDialog = bottomSheetDialogBuilder.create()
|
|
|
|
bottomSheetDialog.show()
|
|
|
|
// bottomSheetDialog.setContentView(R.layout.sort_bottom_sheet)
|
|
|
|
val providerList = bottomSheetDialog.findViewById<ListView>(R.id.sort_providers)!!
|
|
|
|
val subtitleList = bottomSheetDialog.findViewById<ListView>(R.id.sort_subtitles)!!
|
|
|
|
if (subTracks.isEmpty()) {
|
|
|
|
bottomSheetDialog.findViewById<LinearLayout>(R.id.sort_subtitles_holder)?.visibility = GONE
|
|
|
|
} else {
|
|
|
|
val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
|
|
|
|
arrayAdapter.add("No Subtitles")
|
|
|
|
arrayAdapter.addAll(subTracks.map { it.name }.filterNotNull())
|
|
|
|
|
|
|
|
subtitleList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
|
|
|
subtitleList.adapter = arrayAdapter
|
|
|
|
|
|
|
|
subtitleList.setOnItemClickListener { _, _, which, _ ->
|
|
|
|
if (which == 0) {
|
|
|
|
remoteMediaClient.setActiveMediaTracks(longArrayOf()) // NO SUBS
|
|
|
|
} else {
|
|
|
|
val font = TextTrackStyle()
|
|
|
|
font.fontFamily = "Google Sans" //TODO FONT SETTINGS
|
|
|
|
font.backgroundColor = 0x00FFFFFF // TRANSPARENT
|
|
|
|
|
|
|
|
font.edgeColor = Color.BLACK
|
|
|
|
font.edgeType = EDGE_TYPE_OUTLINE
|
|
|
|
font.foregroundColor = Color.WHITE
|
|
|
|
font.fontScale = 1.05f
|
|
|
|
|
|
|
|
remoteMediaClient.setTextTrackStyle(font)
|
|
|
|
|
|
|
|
remoteMediaClient.setActiveMediaTracks(longArrayOf(subTracks[which - 1].id))
|
|
|
|
.setResultCallback {
|
|
|
|
if (!it.status.isSuccess) {
|
|
|
|
Log.e(
|
|
|
|
"CHROMECAST", "Failed with status code:" +
|
|
|
|
it.status.statusCode + " > " + it.status.statusMessage
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bottomSheetDialog.dismiss()
|
|
|
|
}
|
|
|
|
}
|
2021-06-14 00:00:29 +00:00
|
|
|
|
2021-06-14 16:58:43 +00:00
|
|
|
//https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation
|
2021-06-14 19:24:07 +00:00
|
|
|
val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl
|
|
|
|
?: remoteMediaClient?.currentItem?.media?.contentId)
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-06-16 16:54:07 +00:00
|
|
|
val sortingMethods = items.map { it.name }.toTypedArray()
|
|
|
|
val sotringIndex = items.indexOfFirst { it.url == contentUrl }
|
|
|
|
|
|
|
|
val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
|
|
|
|
arrayAdapter.addAll(sortingMethods.toMutableList())
|
|
|
|
|
2021-07-02 18:46:18 +00:00
|
|
|
providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
|
|
|
providerList.adapter = arrayAdapter
|
|
|
|
providerList.setItemChecked(sotringIndex, true)
|
2021-06-16 16:54:07 +00:00
|
|
|
|
2021-07-02 18:46:18 +00:00
|
|
|
providerList.setOnItemClickListener { _, _, which, _ ->
|
2021-06-14 16:58:43 +00:00
|
|
|
val epData = holder.episodes[holder.currentEpisodeIndex]
|
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
fun loadMirror(index: Int) {
|
|
|
|
if (holder.currentLinks.size <= index) return
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
val mediaItem = getMediaInfo(
|
|
|
|
epData,
|
|
|
|
holder,
|
|
|
|
index,
|
2021-07-02 18:46:18 +00:00
|
|
|
remoteMediaClient?.mediaInfo?.customData,
|
|
|
|
holder.currentSubtitles,
|
2021-07-01 20:11:33 +00:00
|
|
|
)
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
val startAt = remoteMediaClient?.approximateStreamPosition ?: 0
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
//remoteMediaClient.load(mediaItem, true, startAt)
|
|
|
|
try { // THIS IS VERY IMPORTANT BECAUSE WE NEVER WANT TO AUTOLOAD THE NEXT EPISODE
|
|
|
|
val currentIdIndex = remoteMediaClient?.getItemIndex()
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
val nextId = remoteMediaClient.mediaQueue.itemIds?.get(currentIdIndex?.plus(1) ?: 0)
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
if (currentIdIndex == null && nextId != null) {
|
2021-07-01 20:11:33 +00:00
|
|
|
awaitLinks(
|
|
|
|
remoteMediaClient?.queueInsertAndPlayItem(
|
|
|
|
MediaQueueItem.Builder(
|
|
|
|
mediaItem
|
|
|
|
)
|
|
|
|
.build(),
|
|
|
|
nextId,
|
|
|
|
startAt,
|
|
|
|
JSONObject()
|
|
|
|
)
|
|
|
|
) {
|
2021-06-14 19:24:07 +00:00
|
|
|
loadMirror(index + 1)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
awaitLinks(remoteMediaClient?.load(mediaItem, true, startAt)) {
|
|
|
|
loadMirror(index + 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
awaitLinks(remoteMediaClient?.load(mediaItem, true, startAt)) {
|
|
|
|
loadMirror(index + 1)
|
|
|
|
}
|
2021-06-14 16:58:43 +00:00
|
|
|
}
|
2021-06-14 00:00:29 +00:00
|
|
|
}
|
2021-06-14 19:24:07 +00:00
|
|
|
loadMirror(which)
|
2021-06-14 00:00:29 +00:00
|
|
|
|
2021-06-16 16:54:07 +00:00
|
|
|
bottomSheetDialog.dismiss()
|
2021-06-14 00:00:29 +00:00
|
|
|
}
|
2021-06-06 18:06:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 00:00:29 +00:00
|
|
|
private fun getCurrentMetaData(): MetadataHolder? {
|
|
|
|
return try {
|
2021-06-14 16:58:43 +00:00
|
|
|
val data = remoteMediaClient?.mediaInfo?.customData?.toString()
|
|
|
|
data?.toKotlinObject()
|
2021-06-14 00:00:29 +00:00
|
|
|
} catch (e: Exception) {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 16:58:43 +00:00
|
|
|
var isLoadingMore = false
|
|
|
|
|
2021-06-06 18:06:01 +00:00
|
|
|
override fun onMediaStatusUpdated() {
|
|
|
|
super.onMediaStatusUpdated()
|
2021-06-14 16:58:43 +00:00
|
|
|
val meta = getCurrentMetaData()
|
|
|
|
|
|
|
|
view.visibility = if ((meta?.currentLinks?.size
|
|
|
|
?: 0) > 1
|
|
|
|
) VISIBLE else INVISIBLE
|
|
|
|
try {
|
|
|
|
if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) {
|
|
|
|
|
2021-06-14 19:24:07 +00:00
|
|
|
val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return
|
2021-06-14 16:58:43 +00:00
|
|
|
val itemCount = remoteMediaClient?.mediaQueue?.itemCount
|
|
|
|
|
|
|
|
if (itemCount != null && itemCount - currentIdIndex == 1 && !isLoadingMore) {
|
|
|
|
isLoadingMore = true
|
|
|
|
|
|
|
|
main {
|
|
|
|
val index = meta.currentEpisodeIndex + 1
|
|
|
|
val epData = meta.episodes[index]
|
|
|
|
val links = ArrayList<ExtractorLink>()
|
2021-07-02 18:46:18 +00:00
|
|
|
val subs = ArrayList<SubtitleFile>()
|
2021-06-14 16:58:43 +00:00
|
|
|
|
|
|
|
val res = safeApiCall {
|
2021-07-02 18:46:18 +00:00
|
|
|
getApiFromName(meta.apiName).loadLinks(epData.data, true, { subtitleFile ->
|
|
|
|
if (!subs.any { it.url == subtitleFile.url }) {
|
|
|
|
subs.add(subtitleFile)
|
|
|
|
}
|
|
|
|
}) { link ->
|
|
|
|
if (!links.any { it.url == link.url }) {
|
|
|
|
links.add(link)
|
2021-06-14 16:58:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-16 22:31:41 +00:00
|
|
|
|
2021-06-14 16:58:43 +00:00
|
|
|
if (res is Resource.Success) {
|
|
|
|
val sorted = sortUrls(links)
|
|
|
|
if (sorted.isNotEmpty()) {
|
2021-07-02 18:46:18 +00:00
|
|
|
val jsonCopy = meta.copy(
|
|
|
|
currentLinks = sorted,
|
|
|
|
currentSubtitles = subs,
|
|
|
|
currentEpisodeIndex = index
|
|
|
|
)
|
2021-06-14 16:58:43 +00:00
|
|
|
|
|
|
|
val done = withContext(Dispatchers.IO) {
|
2021-06-14 19:24:07 +00:00
|
|
|
JSONObject(mapper.writeValueAsString(jsonCopy))
|
|
|
|
}
|
|
|
|
|
|
|
|
val mediaInfo = getMediaInfo(
|
|
|
|
epData,
|
|
|
|
jsonCopy,
|
|
|
|
0,
|
2021-07-02 18:46:18 +00:00
|
|
|
done,
|
|
|
|
subs
|
2021-07-01 20:11:33 +00:00
|
|
|
)
|
2021-06-14 19:24:07 +00:00
|
|
|
|
|
|
|
/*fun loadIndex(index: Int) {
|
|
|
|
println("LOAD INDEX::::: $index")
|
|
|
|
if (meta.currentLinks.size <= index) return
|
|
|
|
val info = getMediaInfo(
|
|
|
|
epData,
|
2021-06-14 16:58:43 +00:00
|
|
|
meta,
|
2021-06-14 19:24:07 +00:00
|
|
|
index,
|
|
|
|
done)
|
|
|
|
awaitLinks(remoteMediaClient?.load(info, true, 0)) {
|
|
|
|
loadIndex(index + 1)
|
|
|
|
}
|
|
|
|
}*/
|
|
|
|
|
2021-07-01 20:11:33 +00:00
|
|
|
awaitLinks(
|
|
|
|
remoteMediaClient?.queueAppendItem(
|
|
|
|
MediaQueueItem.Builder(mediaInfo).build(),
|
|
|
|
JSONObject()
|
|
|
|
)
|
|
|
|
) {
|
2021-06-14 19:24:07 +00:00
|
|
|
println("FAILED TO LOAD NEXT ITEM")
|
|
|
|
// loadIndex(1)
|
2021-06-14 16:58:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
isLoadingMore = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
println(e)
|
|
|
|
}
|
2021-06-06 18:06:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionConnected(castSession: CastSession?) {
|
|
|
|
super.onSessionConnected(castSession)
|
2021-06-14 16:58:43 +00:00
|
|
|
remoteMediaClient?.queueSetRepeatMode(REPEAT_MODE_REPEAT_OFF, JSONObject())
|
2021-06-06 18:06:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SkipTimeController(val view: ImageView, forwards: Boolean) : UIController() {
|
|
|
|
init {
|
|
|
|
//val settingsManager = PreferenceManager.getDefaultSharedPreferences()
|
|
|
|
//val time = settingsManager?.getInt("chromecast_tap_time", 30) ?: 30
|
|
|
|
val time = 30
|
2021-06-10 18:21:42 +00:00
|
|
|
//view.setImageResource(if (forwards) R.drawable.netflix_skip_forward else R.drawable.netflix_skip_back)
|
|
|
|
view.setImageResource(if (forwards) R.drawable.go_forward_30 else R.drawable.go_back_30)
|
2021-06-06 18:06:01 +00:00
|
|
|
view.setOnClickListener {
|
|
|
|
remoteMediaClient.seek(remoteMediaClient.approximateStreamPosition + time * 1000 * if (forwards) 1 else -1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ControllerActivity : ExpandedControllerActivity() {
|
|
|
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
|
|
super.onCreateOptionsMenu(menu)
|
|
|
|
menuInflater.inflate(R.menu.cast_expanded_controller_menu, menu)
|
|
|
|
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
val sourcesButton: ImageView = getButtonImageViewAt(0)
|
|
|
|
val skipBackButton: ImageView = getButtonImageViewAt(1)
|
|
|
|
val skipForwardButton: ImageView = getButtonImageViewAt(2)
|
|
|
|
val skipOpButton: ImageView = getButtonImageViewAt(3)
|
2021-06-14 00:00:29 +00:00
|
|
|
uiMediaController.bindViewToUIController(sourcesButton, SelectSourceController(sourcesButton, this))
|
2021-06-06 18:06:01 +00:00
|
|
|
uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false))
|
|
|
|
uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true))
|
2021-06-14 19:24:07 +00:00
|
|
|
uiMediaController.bindViewToUIController(skipOpButton, SkipNextEpisodeController(skipOpButton))
|
2021-07-02 18:46:18 +00:00
|
|
|
|
|
|
|
|
2021-06-16 22:31:41 +00:00
|
|
|
/* val progressBar: CastSeekBar? = findViewById(R.id.cast_seek_bar)
|
2021-06-16 16:54:07 +00:00
|
|
|
|
2021-06-16 22:31:41 +00:00
|
|
|
progressBar?.backgroundTintList = (UIHelper.adjustAlpha(colorFromAttribute(R.attr.colorPrimary), 0.35f))
|
|
|
|
*/
|
2021-06-06 18:06:01 +00:00
|
|
|
}
|
|
|
|
}
|