mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-15 06:40:30 +05:30
Merge pull request #4240 from Bnyro/audio-track-types
Support for different audio track types
This commit is contained in:
commit
1fd905222d
@ -24,7 +24,9 @@ data class PipedStream(
|
|||||||
val fps: Int? = null,
|
val fps: Int? = null,
|
||||||
val audioTrackName: String? = null,
|
val audioTrackName: String? = null,
|
||||||
val audioTrackId: String? = null,
|
val audioTrackId: String? = null,
|
||||||
val contentLength: Long = -1
|
val contentLength: Long = -1,
|
||||||
|
val audioTrackType: String? = null,
|
||||||
|
val audioTrackLocale: String? = null
|
||||||
) {
|
) {
|
||||||
private fun getQualityString(fileName: String): String {
|
private fun getQualityString(fileName: String): String {
|
||||||
return "${fileName}_${quality?.replace(" ", "_")}_$format." +
|
return "${fileName}_${quality?.replace(" ", "_")}_$format." +
|
||||||
|
@ -19,8 +19,10 @@ object DashHelper {
|
|||||||
|
|
||||||
private data class AdapSetInfo(
|
private data class AdapSetInfo(
|
||||||
val mimeType: String,
|
val mimeType: String,
|
||||||
|
val formats: MutableList<PipedStream> = mutableListOf(),
|
||||||
val audioTrackId: String? = null,
|
val audioTrackId: String? = null,
|
||||||
val formats: MutableList<PipedStream> = mutableListOf()
|
val audioTrackType: String? = null,
|
||||||
|
val audioLocale: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
fun createManifest(
|
fun createManifest(
|
||||||
@ -75,7 +77,6 @@ object DashHelper {
|
|||||||
adapSetInfos.add(
|
adapSetInfos.add(
|
||||||
AdapSetInfo(
|
AdapSetInfo(
|
||||||
stream.mimeType!!,
|
stream.mimeType!!,
|
||||||
null,
|
|
||||||
mutableListOf(stream)
|
mutableListOf(stream)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -94,8 +95,10 @@ object DashHelper {
|
|||||||
adapSetInfos.add(
|
adapSetInfos.add(
|
||||||
AdapSetInfo(
|
AdapSetInfo(
|
||||||
stream.mimeType!!,
|
stream.mimeType!!,
|
||||||
|
mutableListOf(stream),
|
||||||
stream.audioTrackId,
|
stream.audioTrackId,
|
||||||
mutableListOf(stream)
|
stream.audioTrackType,
|
||||||
|
stream.audioTrackLocale
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -105,8 +108,24 @@ object DashHelper {
|
|||||||
adapSetElement.setAttribute("mimeType", adapSet.mimeType)
|
adapSetElement.setAttribute("mimeType", adapSet.mimeType)
|
||||||
adapSetElement.setAttribute("startWithSAP", "1")
|
adapSetElement.setAttribute("startWithSAP", "1")
|
||||||
adapSetElement.setAttribute("subsegmentAlignment", "true")
|
adapSetElement.setAttribute("subsegmentAlignment", "true")
|
||||||
|
|
||||||
if (adapSet.audioTrackId != null) {
|
if (adapSet.audioTrackId != null) {
|
||||||
adapSetElement.setAttribute("lang", adapSet.audioTrackId.substring(0, 2))
|
adapSetElement.setAttribute("lang", adapSet.audioTrackId.substring(0, 2))
|
||||||
|
} else if (adapSet.audioLocale != null) {
|
||||||
|
adapSetElement.setAttribute("lang", adapSet.audioLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add the Role element if there is a track type set
|
||||||
|
// This allows distinction between formats marked as original on YouTube and
|
||||||
|
// formats without track type info set
|
||||||
|
if (adapSet.audioTrackType != null) {
|
||||||
|
val roleElement = doc.createElement("Role")
|
||||||
|
roleElement.setAttribute("schemeIdUri", "urn:mpeg:dash:role:2011")
|
||||||
|
roleElement.setAttribute(
|
||||||
|
"value",
|
||||||
|
getRoleValueFromAudioTrackType(adapSet.audioTrackType)
|
||||||
|
)
|
||||||
|
adapSetElement.appendChild(roleElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isVideo = adapSet.mimeType.contains("video")
|
val isVideo = adapSet.mimeType.contains("video")
|
||||||
@ -162,18 +181,34 @@ object DashHelper {
|
|||||||
val baseUrl = doc.createElement("BaseURL")
|
val baseUrl = doc.createElement("BaseURL")
|
||||||
baseUrl.appendChild(doc.createTextNode(ProxyHelper.unwrapUrl(stream.url!!, rewriteUrls)))
|
baseUrl.appendChild(doc.createTextNode(ProxyHelper.unwrapUrl(stream.url!!, rewriteUrls)))
|
||||||
|
|
||||||
val segmentBase = doc.createElement("SegmentBase")
|
representation.appendChild(audioChannelConfiguration)
|
||||||
|
representation.appendChild(baseUrl)
|
||||||
|
representation.appendChild(createSegmentBaseElement(doc, stream))
|
||||||
|
|
||||||
|
return representation
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSegmentBaseElement(
|
||||||
|
document: Document,
|
||||||
|
stream: PipedStream
|
||||||
|
): Element {
|
||||||
|
val segmentBase = document.createElement("SegmentBase")
|
||||||
segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}")
|
segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}")
|
||||||
|
|
||||||
val initialization = doc.createElement("Initialization")
|
val initialization = document.createElement("Initialization")
|
||||||
initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}")
|
initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}")
|
||||||
segmentBase.appendChild(initialization)
|
segmentBase.appendChild(initialization)
|
||||||
|
|
||||||
representation.appendChild(audioChannelConfiguration)
|
return segmentBase
|
||||||
representation.appendChild(baseUrl)
|
}
|
||||||
representation.appendChild(segmentBase)
|
|
||||||
|
|
||||||
return representation
|
private fun getRoleValueFromAudioTrackType(audioTrackType: String): String {
|
||||||
|
return when (audioTrackType.lowercase()) {
|
||||||
|
"descriptive" -> "description"
|
||||||
|
"dubbed" -> "dub"
|
||||||
|
"original" -> "main"
|
||||||
|
else -> "alternate"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVideoRepresentation(
|
private fun createVideoRepresentation(
|
||||||
|
@ -23,6 +23,7 @@ import androidx.core.view.children
|
|||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.PlaybackParameters
|
import androidx.media3.common.PlaybackParameters
|
||||||
|
import androidx.media3.common.Tracks
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.LoadControl
|
import androidx.media3.exoplayer.LoadControl
|
||||||
@ -39,6 +40,7 @@ import com.github.libretube.enums.SbSkipOptions
|
|||||||
import com.github.libretube.obj.PreviewFrame
|
import com.github.libretube.obj.PreviewFrame
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.util.Locale
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ -579,4 +581,140 @@ object PlayerHelper {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the track type string resource corresponding to ExoPlayer role flags used for audio
|
||||||
|
* track types.
|
||||||
|
*
|
||||||
|
* If the role flags doesn't have any role flags used for audio track types, the string
|
||||||
|
* resource `unknown_audio_track_type` is returned.
|
||||||
|
*
|
||||||
|
* @param context a context to get the string resources used to build the audio track type
|
||||||
|
* @param roleFlags the ExoPlayer role flags from which the audio track type will be returned
|
||||||
|
* @return the track type string resource corresponding to an ExoPlayer role flag or the
|
||||||
|
* `unknown_audio_track_type` one if no role flags corresponding to the ones used for audio
|
||||||
|
* track types is set
|
||||||
|
*/
|
||||||
|
private fun getDisplayAudioTrackTypeFromFormat(
|
||||||
|
context: Context,
|
||||||
|
@C.RoleFlags roleFlags: Int
|
||||||
|
): String {
|
||||||
|
// These role flags should not be set together, so the first role only take into account
|
||||||
|
// flag which matches
|
||||||
|
return when {
|
||||||
|
// If the flag ROLE_FLAG_DESCRIBES_VIDEO is set, return the descriptive_audio_track
|
||||||
|
// string resource
|
||||||
|
roleFlags and C.ROLE_FLAG_DESCRIBES_VIDEO == C.ROLE_FLAG_DESCRIBES_VIDEO ->
|
||||||
|
context.getString(R.string.descriptive_audio_track)
|
||||||
|
|
||||||
|
// If the flag ROLE_FLAG_DESCRIBES_VIDEO is set, return the dubbed_audio_track
|
||||||
|
// string resource
|
||||||
|
roleFlags and C.ROLE_FLAG_DUB == C.ROLE_FLAG_DUB ->
|
||||||
|
context.getString(R.string.dubbed_audio_track)
|
||||||
|
|
||||||
|
// If the flag ROLE_FLAG_DESCRIBES_VIDEO is set, return the original_or_main_audio_track
|
||||||
|
// string resource
|
||||||
|
roleFlags and C.ROLE_FLAG_MAIN == C.ROLE_FLAG_MAIN ->
|
||||||
|
context.getString(R.string.original_or_main_audio_track)
|
||||||
|
|
||||||
|
// Return the unknown_audio_track_type string resource for any other value
|
||||||
|
else -> context.getString(R.string.unknown_audio_track_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an audio track name from an audio format, using its language tag and its role flags.
|
||||||
|
*
|
||||||
|
* If the given language is `null`, the string resource `unknown_audio_language` is used
|
||||||
|
* instead and when the given role flags have no track type value used by the app, the string
|
||||||
|
* resource `unknown_audio_track_type` is used instead.
|
||||||
|
*
|
||||||
|
* @param context a context to get the string resources used to build the
|
||||||
|
* audio track name
|
||||||
|
* @param audioLanguageAndRoleFlags a pair of an audio format language tag and role flags from
|
||||||
|
* which the audio track name will be built
|
||||||
|
* @return an audio track name of an audio format language and role flags, localized according
|
||||||
|
* to the language preferences of the user
|
||||||
|
*/
|
||||||
|
fun getAudioTrackNameFromFormat(
|
||||||
|
context: Context,
|
||||||
|
audioLanguageAndRoleFlags: Pair<String?, @C.RoleFlags Int>
|
||||||
|
): String {
|
||||||
|
val audioLanguage = audioLanguageAndRoleFlags.first
|
||||||
|
return context.getString(R.string.audio_track_format)
|
||||||
|
.format(
|
||||||
|
if (audioLanguage == null) context.getString(R.string.unknown_audio_language)
|
||||||
|
else Locale.forLanguageTag(audioLanguage)
|
||||||
|
.getDisplayLanguage(
|
||||||
|
LocaleHelper.getAppLocale()
|
||||||
|
)
|
||||||
|
.ifEmpty { context.getString(R.string.unknown_audio_language) },
|
||||||
|
getDisplayAudioTrackTypeFromFormat(context, audioLanguageAndRoleFlags.second)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get audio languages with their role flags of supported formats from ExoPlayer track groups
|
||||||
|
* and only the selected ones if requested.
|
||||||
|
*
|
||||||
|
* Duplicate audio languages with their role flags are removed.
|
||||||
|
*
|
||||||
|
* @param groups the list of [Group]s of the current tracks played by the player
|
||||||
|
* @param keepOnlySelectedTracks whether to get only the selected audio languages with their
|
||||||
|
* role flags among the supported ones
|
||||||
|
* @return a list of distinct audio languages with their role flags from the supported formats
|
||||||
|
* of the given track groups and only the selected ones if requested
|
||||||
|
*/
|
||||||
|
fun getAudioLanguagesAndRoleFlagsFromTrackGroups(
|
||||||
|
groups: List<Tracks.Group>,
|
||||||
|
keepOnlySelectedTracks: Boolean
|
||||||
|
): List<Pair<String?, @C.RoleFlags Int>> {
|
||||||
|
// Filter unsupported tracks and keep only selected tracks if requested
|
||||||
|
// Use a lambda expression to avoid checking on each audio format if we keep only selected
|
||||||
|
// tracks or not
|
||||||
|
val trackFilter = if (keepOnlySelectedTracks)
|
||||||
|
{ group: Tracks.Group, trackIndex: Int ->
|
||||||
|
group.isTrackSupported(trackIndex) && group.isTrackSelected(
|
||||||
|
trackIndex
|
||||||
|
)
|
||||||
|
} else { group: Tracks.Group, trackIndex: Int -> group.isTrackSupported(trackIndex) }
|
||||||
|
|
||||||
|
return groups.filter {
|
||||||
|
it.type == C.TRACK_TYPE_AUDIO
|
||||||
|
}.flatMap { group ->
|
||||||
|
(0 until group.length).filter {
|
||||||
|
trackFilter(group, it)
|
||||||
|
}.map { group.getTrackFormat(it) }
|
||||||
|
}.map { format ->
|
||||||
|
format.language to format.roleFlags
|
||||||
|
}.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given flag is set in the given bitfield.
|
||||||
|
*
|
||||||
|
* @param bitField a bitfield
|
||||||
|
* @param flag a flag to check its presence in the given bitfield
|
||||||
|
* @return whether the given flag is set in the given bitfield
|
||||||
|
*/
|
||||||
|
private fun isFlagSet(bitField: Int, flag: Int): Boolean {
|
||||||
|
return bitField and flag == flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given ExoPlayer role flags contain at least one flag used for audio
|
||||||
|
* track types.
|
||||||
|
*
|
||||||
|
* ExoPlayer role flags currently used for audio track types are [C.ROLE_FLAG_DESCRIBES_VIDEO],
|
||||||
|
* [C.ROLE_FLAG_DUB], [C.ROLE_FLAG_MAIN] and [C.ROLE_FLAG_ALTERNATE].
|
||||||
|
*
|
||||||
|
* @param roleFlags the ExoPlayer role flags to check, an int representing a bitfield
|
||||||
|
* @return whether the provided ExoPlayer flags contain a flag used for audio track types
|
||||||
|
*/
|
||||||
|
fun haveAudioTrackRoleFlagSet(@C.RoleFlags roleFlags: Int): Boolean {
|
||||||
|
return isFlagSet(roleFlags, C.ROLE_FLAG_DESCRIBES_VIDEO)
|
||||||
|
|| isFlagSet(roleFlags, C.ROLE_FLAG_DUB)
|
||||||
|
|| isFlagSet(roleFlags, C.ROLE_FLAG_MAIN)
|
||||||
|
|| isFlagSet(roleFlags, C.ROLE_FLAG_ALTERNATE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ import androidx.media3.common.Player
|
|||||||
import androidx.media3.datasource.DefaultDataSource
|
import androidx.media3.datasource.DefaultDataSource
|
||||||
import androidx.media3.datasource.cronet.CronetDataSource
|
import androidx.media3.datasource.cronet.CronetDataSource
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -55,7 +56,6 @@ import com.github.libretube.api.JsonHelper
|
|||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.api.obj.ChapterSegment
|
import com.github.libretube.api.obj.ChapterSegment
|
||||||
import com.github.libretube.api.obj.Message
|
import com.github.libretube.api.obj.Message
|
||||||
import com.github.libretube.api.obj.PipedStream
|
|
||||||
import com.github.libretube.api.obj.Segment
|
import com.github.libretube.api.obj.Segment
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
@ -113,15 +113,16 @@ import com.github.libretube.util.NowPlayingNotification
|
|||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
import com.github.libretube.util.TextUtils
|
import com.github.libretube.util.TextUtils
|
||||||
import com.github.libretube.util.TextUtils.toTimeInSeconds
|
import com.github.libretube.util.TextUtils.toTimeInSeconds
|
||||||
import java.io.IOException
|
import com.github.libretube.util.YoutubeHlsPlaylistParser
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
@ -164,6 +165,11 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
private lateinit var trackSelector: DefaultTrackSelector
|
private lateinit var trackSelector: DefaultTrackSelector
|
||||||
private var captionLanguage: String? = PlayerHelper.defaultSubtitleCode
|
private var captionLanguage: String? = PlayerHelper.defaultSubtitleCode
|
||||||
|
|
||||||
|
private val cronetDataSourceFactory = CronetDataSource.Factory(
|
||||||
|
CronetHelper.cronetEngine,
|
||||||
|
Executors.newCachedThreadPool()
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chapters and comments
|
* Chapters and comments
|
||||||
*/
|
*/
|
||||||
@ -1189,13 +1195,15 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setMediaSource(uri: Uri, mimeType: String) {
|
private fun createMediaItem(uri: Uri, mimeType: String) = MediaItem.Builder()
|
||||||
val mediaItem = MediaItem.Builder()
|
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
.setMimeType(mimeType)
|
.setMimeType(mimeType)
|
||||||
.setSubtitleConfigurations(subtitles)
|
.setSubtitleConfigurations(subtitles)
|
||||||
.setMetadata(streams)
|
.setMetadata(streams)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
private fun setMediaSource(uri: Uri, mimeType: String) {
|
||||||
|
val mediaItem = createMediaItem(uri, mimeType)
|
||||||
exoPlayer.setMediaItem(mediaItem)
|
exoPlayer.setMediaItem(mediaItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1307,6 +1315,16 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
// HLS
|
// HLS
|
||||||
streams.hls != null -> {
|
streams.hls != null -> {
|
||||||
|
val hlsMediaSourceFactory = HlsMediaSource.Factory(cronetDataSourceFactory)
|
||||||
|
.setPlaylistParserFactory(YoutubeHlsPlaylistParser.Factory())
|
||||||
|
|
||||||
|
val mediaSource = hlsMediaSourceFactory.createMediaSource(
|
||||||
|
createMediaItem(
|
||||||
|
ProxyHelper.unwrapStreamUrl(streams.hls!!).toUri(),
|
||||||
|
MimeTypes.APPLICATION_M3U8,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exoPlayer.setMediaSource(mediaSource)
|
||||||
ProxyHelper.unwrapStreamUrl(streams.hls!!).toUri() to MimeTypes.APPLICATION_M3U8
|
ProxyHelper.unwrapStreamUrl(streams.hls!!).toUri() to MimeTypes.APPLICATION_M3U8
|
||||||
}
|
}
|
||||||
// NO STREAM FOUND
|
// NO STREAM FOUND
|
||||||
@ -1329,9 +1347,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
trackSelector = DefaultTrackSelector(requireContext())
|
trackSelector = DefaultTrackSelector(requireContext())
|
||||||
|
|
||||||
trackSelector.updateParameters {
|
trackSelector.updateParameters {
|
||||||
setPreferredAudioLanguage(
|
setPreferredAudioLanguage(LocaleHelper.getAppLocale().isO3Language)
|
||||||
LocaleHelper.getAppLocale().language.lowercase().substring(0, 2)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exoPlayer = ExoPlayer.Builder(requireContext())
|
exoPlayer = ExoPlayer.Builder(requireContext())
|
||||||
@ -1414,24 +1430,46 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
.show(childFragmentManager)
|
.show(childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAudioStreamGroups(audioStreams: List<PipedStream>?): Map<String?, List<PipedStream>> {
|
|
||||||
return audioStreams.orEmpty()
|
|
||||||
.groupBy { it.audioTrackName }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAudioStreamClicked() {
|
override fun onAudioStreamClicked() {
|
||||||
val audioGroups = getAudioStreamGroups(streams.audioStreams)
|
val context = requireContext()
|
||||||
val audioLanguages = audioGroups.map { it.key ?: getString(R.string.default_audio_track) }
|
val audioLanguagesAndRoleFlags = PlayerHelper.getAudioLanguagesAndRoleFlagsFromTrackGroups(
|
||||||
|
exoPlayer.currentTracks.groups, false
|
||||||
|
)
|
||||||
|
val audioLanguages = audioLanguagesAndRoleFlags.map {
|
||||||
|
PlayerHelper.getAudioTrackNameFromFormat(context, it)
|
||||||
|
}
|
||||||
|
val baseBottomSheet = BaseBottomSheet()
|
||||||
|
|
||||||
BaseBottomSheet()
|
if (audioLanguagesAndRoleFlags.isEmpty()) {
|
||||||
.setSimpleItems(audioLanguages) { index ->
|
baseBottomSheet.setSimpleItems(
|
||||||
val audioStreams = audioGroups.values.elementAt(index)
|
listOf(context.getString(R.string.unknown_or_no_audio)),
|
||||||
val lang = audioStreams.firstOrNull()?.audioTrackId?.substring(0, 2)
|
null
|
||||||
|
)
|
||||||
|
} else if (audioLanguagesAndRoleFlags.size == 1
|
||||||
|
&& audioLanguagesAndRoleFlags[0].first == null
|
||||||
|
&& !PlayerHelper.haveAudioTrackRoleFlagSet(
|
||||||
|
audioLanguagesAndRoleFlags[0].second
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Regardless of audio format or quality, if there is only one audio stream which has
|
||||||
|
// no language and no role flags, it should mean that there is only a single audio
|
||||||
|
// track which has no language or track type set in the video played
|
||||||
|
// Consider it as the default audio track (or unknown)
|
||||||
|
baseBottomSheet.setSimpleItems(
|
||||||
|
listOf(context.getString(R.string.default_or_unknown_audio_track)),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
baseBottomSheet.setSimpleItems(audioLanguages) { index ->
|
||||||
|
val selectedAudioFormat = audioLanguagesAndRoleFlags[index]
|
||||||
trackSelector.updateParameters {
|
trackSelector.updateParameters {
|
||||||
setPreferredAudioLanguage(lang)
|
setPreferredAudioLanguage(selectedAudioFormat.first)
|
||||||
|
setPreferredAudioRoleFlags(selectedAudioFormat.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.show(childFragmentManager)
|
}
|
||||||
|
|
||||||
|
baseBottomSheet.show(childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStatsClicked() {
|
override fun onStatsClicked() {
|
||||||
|
@ -39,9 +39,7 @@ class OnlinePlayerView(
|
|||||||
BottomSheetItem(
|
BottomSheetItem(
|
||||||
context.getString(R.string.audio_track),
|
context.getString(R.string.audio_track),
|
||||||
R.drawable.ic_audio,
|
R.drawable.ic_audio,
|
||||||
{
|
{ getCurrentAudioTrackTitle() }
|
||||||
trackSelector?.parameters?.preferredAudioLanguages?.firstOrNull()
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
playerOptions?.onAudioStreamClicked()
|
playerOptions?.onAudioStreamClicked()
|
||||||
},
|
},
|
||||||
@ -67,6 +65,47 @@ class OnlinePlayerView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getCurrentAudioTrackTitle(): String {
|
||||||
|
if (player == null) {
|
||||||
|
return context.getString(R.string.unknown_or_no_audio)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The player reference should be not changed between the null check
|
||||||
|
// and its access, so a non null assertion should be safe here
|
||||||
|
val selectedAudioLanguagesAndRoleFlags =
|
||||||
|
PlayerHelper.getAudioLanguagesAndRoleFlagsFromTrackGroups(
|
||||||
|
player!!.currentTracks.groups,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
if (selectedAudioLanguagesAndRoleFlags.isEmpty()) {
|
||||||
|
return context.getString(R.string.unknown_or_no_audio)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At most one audio track should be selected regardless of audio
|
||||||
|
// format or quality
|
||||||
|
val firstSelectedAudioFormat = selectedAudioLanguagesAndRoleFlags[0]
|
||||||
|
|
||||||
|
if (selectedAudioLanguagesAndRoleFlags.size == 1
|
||||||
|
&& firstSelectedAudioFormat.first == null
|
||||||
|
&& !PlayerHelper.haveAudioTrackRoleFlagSet(
|
||||||
|
firstSelectedAudioFormat.second
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Regardless of audio format or quality, if there is only one
|
||||||
|
// audio stream which has no language and no role flags, it
|
||||||
|
// should mean that there is only a single audio track which
|
||||||
|
// has no language or track type set in the video played
|
||||||
|
// Consider it as the default audio track (or unknown)
|
||||||
|
return context.getString(R.string.default_or_unknown_audio_track)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlayerHelper.getAudioTrackNameFromFormat(
|
||||||
|
context,
|
||||||
|
firstSelectedAudioFormat
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun initPlayerOptions(
|
fun initPlayerOptions(
|
||||||
playerViewModel: PlayerViewModel,
|
playerViewModel: PlayerViewModel,
|
||||||
viewLifecycleOwner: LifecycleOwner,
|
viewLifecycleOwner: LifecycleOwner,
|
||||||
|
@ -0,0 +1,258 @@
|
|||||||
|
package com.github.libretube.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.Format
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Rendition
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylist
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParserFactory
|
||||||
|
import androidx.media3.exoplayer.upstream.ParsingLoadable
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A YouTube HLS playlist parser which adds role flags to audio formats with track types.
|
||||||
|
*
|
||||||
|
* YouTube does not provide descriptive audio track types in a standard way and there is no standard
|
||||||
|
* way to tell whether an audio track is a dubbed track.
|
||||||
|
*
|
||||||
|
* However, this information is still provided in the track name, a non-standard property
|
||||||
|
* (`YT-EXT-XTAGS` which has its value encoded as a protocol buffer) and the stream manifest URL.
|
||||||
|
*
|
||||||
|
* This playlist parser adds track types to audio formats which have this information, by parsing
|
||||||
|
* the manifest URL of these formats.
|
||||||
|
*
|
||||||
|
* It relies internally on a default [HlsPlaylistParser] and processes audio tracks when the
|
||||||
|
* [HlsPlaylistParser] instance used parsed the manifest.
|
||||||
|
*/
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
class YoutubeHlsPlaylistParser : ParsingLoadable.Parser<HlsPlaylist> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create [YoutubeHlsPlaylistParser] instances.
|
||||||
|
*/
|
||||||
|
class Factory : HlsPlaylistParserFactory {
|
||||||
|
override fun createPlaylistParser() = YoutubeHlsPlaylistParser()
|
||||||
|
|
||||||
|
override fun createPlaylistParser(
|
||||||
|
multivariantPlaylist: HlsMultivariantPlaylist,
|
||||||
|
previousMediaPlaylist: HlsMediaPlaylist?
|
||||||
|
) = YoutubeHlsPlaylistParser(multivariantPlaylist, previousMediaPlaylist)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [HlsPlaylistParser] instance which is used to delegate parsing of HLS manifests.
|
||||||
|
*/
|
||||||
|
private val hlsPlaylistParser: HlsPlaylistParser
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [HlsPlaylistParser] no-parameters constructor
|
||||||
|
*/
|
||||||
|
private constructor() {
|
||||||
|
this.hlsPlaylistParser = HlsPlaylistParser()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [HlsPlaylistParser] constructor with [HlsMultivariantPlaylist] and [HlsMediaPlaylist]
|
||||||
|
* parameters
|
||||||
|
*/
|
||||||
|
private constructor(
|
||||||
|
multivariantPlaylist: HlsMultivariantPlaylist,
|
||||||
|
previousMediaPlaylist: HlsMediaPlaylist?
|
||||||
|
) {
|
||||||
|
this.hlsPlaylistParser = HlsPlaylistParser(multivariantPlaylist, previousMediaPlaylist)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a YouTube HLS playlist.
|
||||||
|
*
|
||||||
|
* If the given HLS playlist type is not a [HlsMultivariantPlaylist], it is returned as it is.
|
||||||
|
*
|
||||||
|
* If that's the case, audios extracted from the playlist are parsed and the good audio track
|
||||||
|
* type is set to each audio, if applicable and if this information is available.
|
||||||
|
*
|
||||||
|
* @param uri the source [Uri] of the response, after any redirection.
|
||||||
|
* @param inputStream an [InputStream] from which the response data can be read.
|
||||||
|
*
|
||||||
|
* @return a [HlsPlaylist] which is either the original one parsed by the delegated
|
||||||
|
* [HlsPlaylistParser] instance or a [HlsMultivariantPlaylist] on which audio formats have been
|
||||||
|
* edited to add the role track type flags to the existing ones on them if needed
|
||||||
|
*/
|
||||||
|
override fun parse(uri: Uri, inputStream: InputStream): HlsPlaylist {
|
||||||
|
val hlsPlaylist = hlsPlaylistParser.parse(uri, inputStream)
|
||||||
|
if (hlsPlaylist !is HlsMultivariantPlaylist) {
|
||||||
|
return hlsPlaylist
|
||||||
|
}
|
||||||
|
|
||||||
|
val hlsMultivariantPlaylist: HlsMultivariantPlaylist = hlsPlaylist
|
||||||
|
|
||||||
|
return HlsMultivariantPlaylist(
|
||||||
|
hlsMultivariantPlaylist.baseUri,
|
||||||
|
hlsMultivariantPlaylist.tags,
|
||||||
|
hlsMultivariantPlaylist.variants,
|
||||||
|
hlsMultivariantPlaylist.videos,
|
||||||
|
getAudioRenditionsWithTrackTypeSet(hlsMultivariantPlaylist.audios),
|
||||||
|
hlsMultivariantPlaylist.subtitles,
|
||||||
|
hlsMultivariantPlaylist.closedCaptions,
|
||||||
|
// YouTube HLS playlists have only demuxed formats, so it should be not needed to parse
|
||||||
|
// the muxed format, as it would be always null in this case
|
||||||
|
hlsMultivariantPlaylist.muxedAudioFormat,
|
||||||
|
hlsMultivariantPlaylist.muxedCaptionFormats,
|
||||||
|
hlsMultivariantPlaylist.hasIndependentSegments,
|
||||||
|
hlsMultivariantPlaylist.variableDefinitions,
|
||||||
|
hlsMultivariantPlaylist.sessionKeyDrmInitData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get audio renditions with track types set on them, if they are not already set.
|
||||||
|
*
|
||||||
|
* This function parses audio track types from the stream manifest URL, by parsing the `acont`
|
||||||
|
* value of the `xtags` property of the value of the `sgoap` "path parameter".
|
||||||
|
* It adds then the corresponding ExoPlayer role flag in the audio format, if it has been not
|
||||||
|
* already set (this should never be the case).
|
||||||
|
*
|
||||||
|
* Any failure when the audio track type property could not parsed when it should (audio track
|
||||||
|
* types are only available on videos with multiple audio tracks) is ignored and the stream is
|
||||||
|
* kept as it is in this case.
|
||||||
|
*
|
||||||
|
* @param hlsMultivariantPlaylistAudios the list of audio [Rendition]s of a
|
||||||
|
* [HlsMultivariantPlaylist]
|
||||||
|
* @return a new list of audio [Rendition]s with audio track types set in the role flags of the
|
||||||
|
* audio formats
|
||||||
|
*/
|
||||||
|
private fun getAudioRenditionsWithTrackTypeSet(
|
||||||
|
hlsMultivariantPlaylistAudios: List<Rendition>
|
||||||
|
): List<Rendition> {
|
||||||
|
return hlsMultivariantPlaylistAudios.map {
|
||||||
|
// Add the audio stream as it is if no path segments has been found
|
||||||
|
// This should never happen, as YouTube always uses path segments for their HLS URLs
|
||||||
|
val pathSegments = it.url?.pathSegments ?: return@map it
|
||||||
|
|
||||||
|
// Path segments after the videoplayback one can be also converted to query parameters
|
||||||
|
// (the contrary is also possible), so these segments work like keys and values in a map
|
||||||
|
val sgoapPathParameterNameIndex = pathSegments.indexOf(SGOAP_PATH_PARAMETER)
|
||||||
|
|
||||||
|
// Return the audio stream as it is if no audio track type parameter has been found
|
||||||
|
if (sgoapPathParameterNameIndex == -1) {
|
||||||
|
return@map it
|
||||||
|
}
|
||||||
|
|
||||||
|
val sgoapPathParameterValueIndex = sgoapPathParameterNameIndex + 1
|
||||||
|
|
||||||
|
if (sgoapPathParameterValueIndex == pathSegments.size) {
|
||||||
|
return@map it
|
||||||
|
}
|
||||||
|
|
||||||
|
Rendition(
|
||||||
|
it.url,
|
||||||
|
createAudioFormatFromAcountValue(
|
||||||
|
pathSegments[sgoapPathParameterValueIndex],
|
||||||
|
it.format
|
||||||
|
),
|
||||||
|
it.groupId,
|
||||||
|
it.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an audio [Format] based on an existing one and the `acont` property value of the
|
||||||
|
* `xtags` one, from a `sgoap` path parameter value.
|
||||||
|
*
|
||||||
|
* If the `acont` property has been found in the `sgoap` path parameter value provided, an
|
||||||
|
* audio track type role flag is added to the existing ones, if it isn't already added, using
|
||||||
|
* [getFullAudioRoleFlags]; otherwise, the format is kept as it is.
|
||||||
|
*
|
||||||
|
* @param sgoapPathParameterValue a `sgoap` path parameter value
|
||||||
|
* @param audioFormat the audio format linked to the URL from which the
|
||||||
|
* `sgoapPathParameterValue` parameter comes from
|
||||||
|
* @return an [Format] based of the original one provided or the original one if the `acont`
|
||||||
|
* property has been not found
|
||||||
|
*/
|
||||||
|
private fun createAudioFormatFromAcountValue(
|
||||||
|
sgoapPathParameterValue: String,
|
||||||
|
audioFormat: Format
|
||||||
|
): Format {
|
||||||
|
XTAGS_ACONT_VALUE_REGEX.find(sgoapPathParameterValue)?.groupValues?.get(1)
|
||||||
|
?.let { acontValue ->
|
||||||
|
return audioFormat.buildUpon()
|
||||||
|
.setRoleFlags(
|
||||||
|
getFullAudioRoleFlags(
|
||||||
|
audioFormat.roleFlags,
|
||||||
|
acontValue
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no info about format being original, dubbed or descriptive, return the format as it is
|
||||||
|
return audioFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full audio role flags of an audio track.
|
||||||
|
*
|
||||||
|
* Full role flags are the existing flags parsed by ExoPlayer and the flags coming from the
|
||||||
|
* audio track type parsed from the `acont` property value of the stream manifest URL.
|
||||||
|
*
|
||||||
|
* The following table describes what value is parsed
|
||||||
|
*
|
||||||
|
* | `acont` value | Role flag added from [ExoPlayer track role flags][C.RoleFlags] |
|
||||||
|
* | ------------- | ------------- |
|
||||||
|
* | `dubbed` | [C.ROLE_FLAG_DUB] |
|
||||||
|
* | `descriptive` | [C.ROLE_FLAG_DESCRIBES_VIDEO] |
|
||||||
|
* | `original` | [C.ROLE_FLAG_MAIN] |
|
||||||
|
* | everything else | [C.ROLE_FLAG_ALTERNATE] |
|
||||||
|
*
|
||||||
|
* @param roleFlags the current role flags of the audio track
|
||||||
|
* @param acontValue the value of the `acont` property
|
||||||
|
* @return the full audio role flags of the audio track like described above
|
||||||
|
*/
|
||||||
|
private fun getFullAudioRoleFlags(
|
||||||
|
roleFlags: Int,
|
||||||
|
acontValue: String
|
||||||
|
): Int {
|
||||||
|
val acontRoleFlags = when (acontValue.lowercase()) {
|
||||||
|
"dubbed" -> C.ROLE_FLAG_DUB
|
||||||
|
"descriptive" -> C.ROLE_FLAG_DESCRIBES_VIDEO
|
||||||
|
"original" -> C.ROLE_FLAG_MAIN
|
||||||
|
// Original audio tracks without other audio track should not have the `acont` property
|
||||||
|
// nor the `xtags` one, so the the track should be not set as the main one
|
||||||
|
// The alternate role flag should be the most relevant flag in this case
|
||||||
|
else -> C.ROLE_FLAG_ALTERNATE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this flag to the existing ones (if it has been not already added) and return the
|
||||||
|
// result of this operation
|
||||||
|
return roleFlags or acontRoleFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant for the `sgoap` "path parameter" name.
|
||||||
|
*
|
||||||
|
* YouTube HLS streams are for most of them, the same streams delivered as the DASH ones.
|
||||||
|
* The service provide information on the original stream of an HLS stream URL in "path
|
||||||
|
* parameters", `sgovp` for video streams and `sgoap` for audio streams.
|
||||||
|
*
|
||||||
|
* This information should include, for audio streams, the track type when there is multiple
|
||||||
|
* audio tracks in a video, which is what we need to get.
|
||||||
|
*/
|
||||||
|
private const val SGOAP_PATH_PARAMETER = "sgoap"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression to find the `acont` property value of the `xtags` property value from
|
||||||
|
* a `sgoap` "path parameter" value of a YouTube HLS streaming URL.
|
||||||
|
*
|
||||||
|
* The `acont` property provides the track type of an audio stream, when a video of the
|
||||||
|
* service has multiple audio tracks.
|
||||||
|
*/
|
||||||
|
private val XTAGS_ACONT_VALUE_REGEX = Regex("xtags=.*acont=(.[^:]+)")
|
||||||
|
}
|
||||||
|
}
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout">تخطيط المشغل البديل</string>
|
<string name="alternative_player_layout">تخطيط المشغل البديل</string>
|
||||||
<string name="alternative_player_layout_summary">وتظهر أشرطة الفيديو ذات الصلة كصف أعلى من التعليقات بدلا من التعليقات الواردة أدناه.</string>
|
<string name="alternative_player_layout_summary">وتظهر أشرطة الفيديو ذات الصلة كصف أعلى من التعليقات بدلا من التعليقات الواردة أدناه.</string>
|
||||||
<string name="audio_track">مسار صوتي</string>
|
<string name="audio_track">مسار صوتي</string>
|
||||||
<string name="default_audio_track">الإفتراضي</string>
|
|
||||||
<string name="hls_instead_of_dash">استخدام HLS</string>
|
<string name="hls_instead_of_dash">استخدام HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">استخدم HLS بدلا من DASH (سيكون أبطأ ، غير مستحسن)</string>
|
<string name="hls_instead_of_dash_summary">استخدم HLS بدلا من DASH (سيكون أبطأ ، غير مستحسن)</string>
|
||||||
<string name="auto_quality">تلقائي</string>
|
<string name="auto_quality">تلقائي</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout">Alternativ oynadıcı tərtibatı</string>
|
<string name="alternative_player_layout">Alternativ oynadıcı tərtibatı</string>
|
||||||
<string name="alternative_player_layout_summary">Əlaqəli videoları aşağı əvəzinə, şərhlər üzərində cərgə kimi göstər.</string>
|
<string name="alternative_player_layout_summary">Əlaqəli videoları aşağı əvəzinə, şərhlər üzərində cərgə kimi göstər.</string>
|
||||||
<string name="audio_track">Səs axını</string>
|
<string name="audio_track">Səs axını</string>
|
||||||
<string name="default_audio_track">Standart</string>
|
|
||||||
<string name="auto_quality">Avtomatik</string>
|
<string name="auto_quality">Avtomatik</string>
|
||||||
<string name="hls_instead_of_dash">HLS istifadə et</string>
|
<string name="hls_instead_of_dash">HLS istifadə et</string>
|
||||||
<string name="hls_instead_of_dash_summary">DASH əvəzinə HLS istifadə et (daha yavaş olacaq, tövsiyə edilmədi)</string>
|
<string name="hls_instead_of_dash_summary">DASH əvəzinə HLS istifadə et (daha yavaş olacaq, tövsiyə edilmədi)</string>
|
||||||
|
@ -304,7 +304,6 @@
|
|||||||
<string name="notification_time_summary">Прамежак часу, у які дазволена паказваць апавяшчэнні.</string>
|
<string name="notification_time_summary">Прамежак часу, у які дазволена паказваць апавяшчэнні.</string>
|
||||||
<string name="alternative_player_layout_summary">Паказваць звязаныя відэа ў радку над каментарыямі, а не ўнізе.</string>
|
<string name="alternative_player_layout_summary">Паказваць звязаныя відэа ў радку над каментарыямі, а не ўнізе.</string>
|
||||||
<string name="audio_track">Гукавая дарожка</string>
|
<string name="audio_track">Гукавая дарожка</string>
|
||||||
<string name="default_audio_track">Па змаўчанні</string>
|
|
||||||
<string name="hls_instead_of_dash">Выкарыстоўвайце HLS</string>
|
<string name="hls_instead_of_dash">Выкарыстоўвайце HLS</string>
|
||||||
<string name="auto_quality">Аўто</string>
|
<string name="auto_quality">Аўто</string>
|
||||||
<string name="unsupported_file_format">Фармат файла не падтрымліваецца: %1$s</string>
|
<string name="unsupported_file_format">Фармат файла не падтрымліваецца: %1$s</string>
|
||||||
|
@ -309,7 +309,6 @@
|
|||||||
<string name="layout">লেয়াউট</string>
|
<string name="layout">লেয়াউট</string>
|
||||||
<string name="alternative_player_layout">বিকল্প প্লেয়ার লেয়াউট</string>
|
<string name="alternative_player_layout">বিকল্প প্লেয়ার লেয়াউট</string>
|
||||||
<string name="audio_track">অডিও ট্র্যাক</string>
|
<string name="audio_track">অডিও ট্র্যাক</string>
|
||||||
<string name="default_audio_track">ডিফল্ট</string>
|
|
||||||
<string name="hls_instead_of_dash">HLS ব্যাবহার করুন</string>
|
<string name="hls_instead_of_dash">HLS ব্যাবহার করুন</string>
|
||||||
<string name="hls_instead_of_dash_summary">DASH এর বদলে HLS ব্যাবহার করুন (ধীরগতিসম্পন্ন হতে পারে, সুপারিশকৃত নয়)</string>
|
<string name="hls_instead_of_dash_summary">DASH এর বদলে HLS ব্যাবহার করুন (ধীরগতিসম্পন্ন হতে পারে, সুপারিশকৃত নয়)</string>
|
||||||
<string name="auto_quality">স্বয়ংক্রিয়</string>
|
<string name="auto_quality">স্বয়ংক্রিয়</string>
|
||||||
|
@ -331,7 +331,6 @@
|
|||||||
<string name="notification_time_summary">دیاریکردنی مەودای کاتی ئاگادارکردنەوەکان بۆ پیشاندانی بڵاوکراوە.</string>
|
<string name="notification_time_summary">دیاریکردنی مەودای کاتی ئاگادارکردنەوەکان بۆ پیشاندانی بڵاوکراوە.</string>
|
||||||
<string name="alternative_player_layout_summary">پیشاندانی ڤیدیۆ هاوشێوەکان لەسەرووی بەشی لێدوانەکان.</string>
|
<string name="alternative_player_layout_summary">پیشاندانی ڤیدیۆ هاوشێوەکان لەسەرووی بەشی لێدوانەکان.</string>
|
||||||
<string name="audio_track">تراکی دەنگی</string>
|
<string name="audio_track">تراکی دەنگی</string>
|
||||||
<string name="default_audio_track">بنەڕەتی</string>
|
|
||||||
<string name="limit_to_runtime">سنوردارکردن بۆ لێدان</string>
|
<string name="limit_to_runtime">سنوردارکردن بۆ لێدان</string>
|
||||||
<string name="trends">پێشنیارکراوەکان</string>
|
<string name="trends">پێشنیارکراوەکان</string>
|
||||||
<string name="featured">هەڵبژێردراوەکان</string>
|
<string name="featured">هەڵبژێردراوەکان</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="layout">Rozložení</string>
|
<string name="layout">Rozložení</string>
|
||||||
<string name="alternative_player_layout">Alternativní rozložení přehrávače</string>
|
<string name="alternative_player_layout">Alternativní rozložení přehrávače</string>
|
||||||
<string name="audio_track">Zvuková stopa</string>
|
<string name="audio_track">Zvuková stopa</string>
|
||||||
<string name="default_audio_track">Výchozí</string>
|
|
||||||
<string name="auto_quality">Auto</string>
|
<string name="auto_quality">Auto</string>
|
||||||
<string name="hls_instead_of_dash">Použít HLS</string>
|
<string name="hls_instead_of_dash">Použít HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Použít HLS místo DASH (bude pomalejší, nedoporučuje se)</string>
|
<string name="hls_instead_of_dash_summary">Použít HLS místo DASH (bude pomalejší, nedoporučuje se)</string>
|
||||||
|
@ -309,7 +309,6 @@
|
|||||||
<string name="added_to_playlist">Zur Playlist %1$s hinzugefügt</string>
|
<string name="added_to_playlist">Zur Playlist %1$s hinzugefügt</string>
|
||||||
<string name="sb_markers">Markierungen</string>
|
<string name="sb_markers">Markierungen</string>
|
||||||
<string name="audio_track">Tonspur</string>
|
<string name="audio_track">Tonspur</string>
|
||||||
<string name="default_audio_track">Standard</string>
|
|
||||||
<string name="livestreams">Livestreams</string>
|
<string name="livestreams">Livestreams</string>
|
||||||
<string name="alternative_videos_layout">Alternatives Video-Layout</string>
|
<string name="alternative_videos_layout">Alternatives Video-Layout</string>
|
||||||
<string name="hls_instead_of_dash">HLS verwenden</string>
|
<string name="hls_instead_of_dash">HLS verwenden</string>
|
||||||
|
@ -367,7 +367,6 @@
|
|||||||
<string name="import_playlists">Εισαγωγή λιστών αναπαραγωγής</string>
|
<string name="import_playlists">Εισαγωγή λιστών αναπαραγωγής</string>
|
||||||
<string name="export_playlists">Εξαγωγή λιστών αναπαραγωγής</string>
|
<string name="export_playlists">Εξαγωγή λιστών αναπαραγωγής</string>
|
||||||
<string name="audio_track">Ηχητικό κομμάτι</string>
|
<string name="audio_track">Ηχητικό κομμάτι</string>
|
||||||
<string name="default_audio_track">Προεπιλογή</string>
|
|
||||||
<string name="audio_player">Αναπαραγωγή ήχου</string>
|
<string name="audio_player">Αναπαραγωγή ήχου</string>
|
||||||
<string name="renamePlaylist">Μετονομασία λίστας αναπαραγωγής</string>
|
<string name="renamePlaylist">Μετονομασία λίστας αναπαραγωγής</string>
|
||||||
<string name="queue_insert_related_videos">Εισαγάγετε σχετικά βίντεο</string>
|
<string name="queue_insert_related_videos">Εισαγάγετε σχετικά βίντεο</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="navbar_order">Ordenar</string>
|
<string name="navbar_order">Ordenar</string>
|
||||||
<string name="layout">Diseño</string>
|
<string name="layout">Diseño</string>
|
||||||
<string name="audio_track">Pista de audio</string>
|
<string name="audio_track">Pista de audio</string>
|
||||||
<string name="default_audio_track">Predeterminada</string>
|
|
||||||
<string name="auto_quality">Automatico</string>
|
<string name="auto_quality">Automatico</string>
|
||||||
<string name="hls_instead_of_dash_summary">Usar HLS en vez de DASH (podría ser lento, no recomendado)</string>
|
<string name="hls_instead_of_dash_summary">Usar HLS en vez de DASH (podría ser lento, no recomendado)</string>
|
||||||
<string name="hls_instead_of_dash">Usar HLS</string>
|
<string name="hls_instead_of_dash">Usar HLS</string>
|
||||||
|
@ -319,7 +319,6 @@
|
|||||||
<string name="pinch_control_summary">Erabil atximur egiteko keinua, hurbiltzeko/ urruntzeko.</string>
|
<string name="pinch_control_summary">Erabil atximur egiteko keinua, hurbiltzeko/ urruntzeko.</string>
|
||||||
<string name="playback_pitch">Tonua</string>
|
<string name="playback_pitch">Tonua</string>
|
||||||
<string name="audio_track">Audio pista</string>
|
<string name="audio_track">Audio pista</string>
|
||||||
<string name="default_audio_track">Lehenetsia</string>
|
|
||||||
<string name="defaults">Lehenetsia</string>
|
<string name="defaults">Lehenetsia</string>
|
||||||
<string name="alternative_player_layout_summary">Bistaratu erlazionatutako bideoak errenkada gisa iruzkinen gainetik, behean agertu beharrean.</string>
|
<string name="alternative_player_layout_summary">Bistaratu erlazionatutako bideoak errenkada gisa iruzkinen gainetik, behean agertu beharrean.</string>
|
||||||
<string name="hls_instead_of_dash">HLS erabili</string>
|
<string name="hls_instead_of_dash">HLS erabili</string>
|
||||||
|
@ -242,7 +242,6 @@
|
|||||||
<string name="preferences">ترجیحات</string>
|
<string name="preferences">ترجیحات</string>
|
||||||
<string name="queue">صف</string>
|
<string name="queue">صف</string>
|
||||||
<string name="navigation_bar">نوار ناوبری</string>
|
<string name="navigation_bar">نوار ناوبری</string>
|
||||||
<string name="default_audio_track">پیشگزیده</string>
|
|
||||||
<string name="hls_instead_of_dash">استفاده از HLS</string>
|
<string name="hls_instead_of_dash">استفاده از HLS</string>
|
||||||
<string name="auto_quality">خودکار</string>
|
<string name="auto_quality">خودکار</string>
|
||||||
<string name="trending">آنچه اکنون داغ است</string>
|
<string name="trending">آنچه اکنون داغ است</string>
|
||||||
|
@ -348,7 +348,6 @@
|
|||||||
<string name="notification_time">Ilmoitusaika</string>
|
<string name="notification_time">Ilmoitusaika</string>
|
||||||
<string name="navbar_order">Järjestys</string>
|
<string name="navbar_order">Järjestys</string>
|
||||||
<string name="audio_track">Ääniraita</string>
|
<string name="audio_track">Ääniraita</string>
|
||||||
<string name="default_audio_track">Oletus</string>
|
|
||||||
<string name="hls_instead_of_dash_summary">Käytä HLS:ää DASHin sijaan (on hitaampi, ei suositella)</string>
|
<string name="hls_instead_of_dash_summary">Käytä HLS:ää DASHin sijaan (on hitaampi, ei suositella)</string>
|
||||||
<string name="auto_quality">Auto</string>
|
<string name="auto_quality">Auto</string>
|
||||||
<string name="limit_to_runtime">Rajoita suoritusaikaa</string>
|
<string name="limit_to_runtime">Rajoita suoritusaikaa</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="layout">Mise en page</string>
|
<string name="layout">Mise en page</string>
|
||||||
<string name="alternative_player_layout_summary">Afficher les vidéos connexes en une ligne au-dessus des commentaires, à la place d\'au-dessous.</string>
|
<string name="alternative_player_layout_summary">Afficher les vidéos connexes en une ligne au-dessus des commentaires, à la place d\'au-dessous.</string>
|
||||||
<string name="audio_track">Piste audio</string>
|
<string name="audio_track">Piste audio</string>
|
||||||
<string name="default_audio_track">Par défaut</string>
|
|
||||||
<string name="hls_instead_of_dash">Utiliser HLS</string>
|
<string name="hls_instead_of_dash">Utiliser HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Utiliser HLS à la place de DASH (sera plus lent, non recommandé)</string>
|
<string name="hls_instead_of_dash_summary">Utiliser HLS à la place de DASH (sera plus lent, non recommandé)</string>
|
||||||
<string name="auto_quality">Automatique</string>
|
<string name="auto_quality">Automatique</string>
|
||||||
|
@ -241,7 +241,6 @@
|
|||||||
<string name="videoCount">%1$d વિડિયો</string>
|
<string name="videoCount">%1$d વિડિયો</string>
|
||||||
<string name="retry">પુન:પ્રયત્ન કરો</string>
|
<string name="retry">પુન:પ્રયત્ન કરો</string>
|
||||||
<string name="comments">ટિપ્પણીઓ</string>
|
<string name="comments">ટિપ્પણીઓ</string>
|
||||||
<string name="default_audio_track">મૂળભૂત</string>
|
|
||||||
<string name="downloads">ડાઉનલોડ્સ</string>
|
<string name="downloads">ડાઉનલોડ્સ</string>
|
||||||
<string name="livestreams">લાઇવસ્ટ્રીમ્સ</string>
|
<string name="livestreams">લાઇવસ્ટ્રીમ્સ</string>
|
||||||
<string name="update_summary">સુધારા માટે ચકાસો</string>
|
<string name="update_summary">સુધારા માટે ચકાસો</string>
|
||||||
|
@ -198,7 +198,6 @@
|
|||||||
<string name="sb_markers_summary">समय पट्टी पर खंडों को चिह्नित करें।</string>
|
<string name="sb_markers_summary">समय पट्टी पर खंडों को चिह्नित करें।</string>
|
||||||
<string name="update_available">संस्करण %1$s उपलब्ध है</string>
|
<string name="update_available">संस्करण %1$s उपलब्ध है</string>
|
||||||
<string name="audio_track">ऑडियो ट्रैक</string>
|
<string name="audio_track">ऑडियो ट्रैक</string>
|
||||||
<string name="default_audio_track">डिफ़ॉल्ट</string>
|
|
||||||
<string name="downloads">डाउनलोडस</string>
|
<string name="downloads">डाउनलोडस</string>
|
||||||
<string name="livestreams">लाइव स्ट्रीम</string>
|
<string name="livestreams">लाइव स्ट्रीम</string>
|
||||||
<string name="alternative_videos_layout">वैकल्पिक वीडियो लेआउट</string>
|
<string name="alternative_videos_layout">वैकल्पिक वीडियो लेआउट</string>
|
||||||
|
@ -310,7 +310,6 @@
|
|||||||
<string name="alternative_player_layout">Alternatív lejátszó elrendezés</string>
|
<string name="alternative_player_layout">Alternatív lejátszó elrendezés</string>
|
||||||
<string name="navbar_order">Sorrend</string>
|
<string name="navbar_order">Sorrend</string>
|
||||||
<string name="alternative_player_layout_summary">A kapcsolódó videók megjelenítése egy sorban a hozzászólások felett, nem pedig alattuk.</string>
|
<string name="alternative_player_layout_summary">A kapcsolódó videók megjelenítése egy sorban a hozzászólások felett, nem pedig alattuk.</string>
|
||||||
<string name="default_audio_track">Alapérték</string>
|
|
||||||
<string name="audio_track">Hangsáv</string>
|
<string name="audio_track">Hangsáv</string>
|
||||||
<string name="hls_instead_of_dash">HLS használata</string>
|
<string name="hls_instead_of_dash">HLS használata</string>
|
||||||
<string name="auto_quality">Automatikus</string>
|
<string name="auto_quality">Automatikus</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout">Tata letak pemutar alternatif</string>
|
<string name="alternative_player_layout">Tata letak pemutar alternatif</string>
|
||||||
<string name="alternative_player_layout_summary">Tampilkan video terkait sebagai baris di atas komentar, bukan di bawah.</string>
|
<string name="alternative_player_layout_summary">Tampilkan video terkait sebagai baris di atas komentar, bukan di bawah.</string>
|
||||||
<string name="audio_track">Trek audio</string>
|
<string name="audio_track">Trek audio</string>
|
||||||
<string name="default_audio_track">Bawaan</string>
|
|
||||||
<string name="hls_instead_of_dash">Gunakan HLS</string>
|
<string name="hls_instead_of_dash">Gunakan HLS</string>
|
||||||
<string name="auto_quality">Otomatis</string>
|
<string name="auto_quality">Otomatis</string>
|
||||||
<string name="hls_instead_of_dash_summary">Gunakan HLS daripada DASH (akan lebih lambat, tidak disarankan)</string>
|
<string name="hls_instead_of_dash_summary">Gunakan HLS daripada DASH (akan lebih lambat, tidak disarankan)</string>
|
||||||
|
@ -318,7 +318,6 @@
|
|||||||
<string name="layout">Disposizione</string>
|
<string name="layout">Disposizione</string>
|
||||||
<string name="alternative_player_layout">Layout alternativo del player</string>
|
<string name="alternative_player_layout">Layout alternativo del player</string>
|
||||||
<string name="audio_track">Traccia audio</string>
|
<string name="audio_track">Traccia audio</string>
|
||||||
<string name="default_audio_track">Predefinita</string>
|
|
||||||
<string name="hls_instead_of_dash">Usa HLS</string>
|
<string name="hls_instead_of_dash">Usa HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Usa HLS invece di DASH (sarà più lento, non consigliato)</string>
|
<string name="hls_instead_of_dash_summary">Usa HLS invece di DASH (sarà più lento, non consigliato)</string>
|
||||||
<string name="auto_quality">Automatica</string>
|
<string name="auto_quality">Automatica</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="layout">פריסה</string>
|
<string name="layout">פריסה</string>
|
||||||
<string name="alternative_player_layout">פריסת נגן חלופית</string>
|
<string name="alternative_player_layout">פריסת נגן חלופית</string>
|
||||||
<string name="audio_track">רצועת שמע</string>
|
<string name="audio_track">רצועת שמע</string>
|
||||||
<string name="default_audio_track">ברירת מחדל</string>
|
|
||||||
<string name="auto_quality">אוטומטי</string>
|
<string name="auto_quality">אוטומטי</string>
|
||||||
<string name="hls_instead_of_dash">להשתמש בתזרים</string>
|
<string name="hls_instead_of_dash">להשתמש בתזרים</string>
|
||||||
<string name="hls_instead_of_dash_summary">להשתמש בתזרים (HLS) במקום ב־DASH (אטי יותר, לא מומלץ)</string>
|
<string name="hls_instead_of_dash_summary">להשתמש בתזרים (HLS) במקום ב־DASH (אטי יותר, לא מומלץ)</string>
|
||||||
|
@ -340,7 +340,6 @@
|
|||||||
<string name="proceed">続ける</string>
|
<string name="proceed">続ける</string>
|
||||||
<string name="play_latest_videos">最新の動画を再生</string>
|
<string name="play_latest_videos">最新の動画を再生</string>
|
||||||
<string name="audio_track">音声トラック</string>
|
<string name="audio_track">音声トラック</string>
|
||||||
<string name="default_audio_track">標準</string>
|
|
||||||
<string name="hls_instead_of_dash_summary">DASHの代わりにHLSを使用します (より低速、非推奨)</string>
|
<string name="hls_instead_of_dash_summary">DASHの代わりにHLSを使用します (より低速、非推奨)</string>
|
||||||
<string name="hls_instead_of_dash">HLSを使う</string>
|
<string name="hls_instead_of_dash">HLSを使う</string>
|
||||||
<string name="auto_quality">自動</string>
|
<string name="auto_quality">自動</string>
|
||||||
|
@ -240,7 +240,6 @@
|
|||||||
<string name="import_playlists">재생목록 가져오기</string>
|
<string name="import_playlists">재생목록 가져오기</string>
|
||||||
<string name="export_playlists">재생목록 내보내기</string>
|
<string name="export_playlists">재생목록 내보내기</string>
|
||||||
<string name="audio_track">오디오 트랙</string>
|
<string name="audio_track">오디오 트랙</string>
|
||||||
<string name="default_audio_track">기본값</string>
|
|
||||||
<string name="hls_instead_of_dash">HLS를 사용하세요</string>
|
<string name="hls_instead_of_dash">HLS를 사용하세요</string>
|
||||||
<string name="limit_to_runtime">런타임으로 제한</string>
|
<string name="limit_to_runtime">런타임으로 제한</string>
|
||||||
<string name="trends">트렌드</string>
|
<string name="trends">트렌드</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout_summary">Rodyti susijusius vaizdo įrašus kaip eilutę virš komentarų, o ne kaip įprasta po jais.</string>
|
<string name="alternative_player_layout_summary">Rodyti susijusius vaizdo įrašus kaip eilutę virš komentarų, o ne kaip įprasta po jais.</string>
|
||||||
<string name="alternative_player_layout">Alternatyvus grotuvo išdėstymas</string>
|
<string name="alternative_player_layout">Alternatyvus grotuvo išdėstymas</string>
|
||||||
<string name="audio_track">Garso takelis</string>
|
<string name="audio_track">Garso takelis</string>
|
||||||
<string name="default_audio_track">Numatytas</string>
|
|
||||||
<string name="auto_quality">Automatinė</string>
|
<string name="auto_quality">Automatinė</string>
|
||||||
<string name="hls_instead_of_dash">Naudoti HLS</string>
|
<string name="hls_instead_of_dash">Naudoti HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Vietoj DASH naudoti HLS (veiks lėčiau, nerekomenduojama)</string>
|
<string name="hls_instead_of_dash_summary">Vietoj DASH naudoti HLS (veiks lėčiau, nerekomenduojama)</string>
|
||||||
|
@ -339,7 +339,6 @@
|
|||||||
<string name="layout">Izklājums</string>
|
<string name="layout">Izklājums</string>
|
||||||
<string name="alternative_player_layout">Alternatīvs atskaņotāja izklājums</string>
|
<string name="alternative_player_layout">Alternatīvs atskaņotāja izklājums</string>
|
||||||
<string name="alternative_player_layout_summary">Rādīt saistītos video rindā virs komentāriem nevis zem tiem.</string>
|
<string name="alternative_player_layout_summary">Rādīt saistītos video rindā virs komentāriem nevis zem tiem.</string>
|
||||||
<string name="default_audio_track">Noklusējumaa</string>
|
|
||||||
<string name="unsupported_file_format">Neatbalstīts faila formāts: %1$s</string>
|
<string name="unsupported_file_format">Neatbalstīts faila formāts: %1$s</string>
|
||||||
<string name="hls_instead_of_dash">Izmantot HLS</string>
|
<string name="hls_instead_of_dash">Izmantot HLS</string>
|
||||||
<string name="auto_quality">Automātiski</string>
|
<string name="auto_quality">Automātiski</string>
|
||||||
|
@ -336,7 +336,6 @@
|
|||||||
<string name="layout">मांडणी</string>
|
<string name="layout">मांडणी</string>
|
||||||
<string name="alternative_player_layout">पर्यायी प्लेअर लेआउट</string>
|
<string name="alternative_player_layout">पर्यायी प्लेअर लेआउट</string>
|
||||||
<string name="audio_track">ऑडिओ ट्रॅक</string>
|
<string name="audio_track">ऑडिओ ट्रॅक</string>
|
||||||
<string name="default_audio_track">डीफॉल्ट</string>
|
|
||||||
<string name="limit_to_runtime">रनटाइमची मर्यादा</string>
|
<string name="limit_to_runtime">रनटाइमची मर्यादा</string>
|
||||||
<string name="queue_insert_related_videos">संबंधित व्हिडिओ समाविष्ट करा</string>
|
<string name="queue_insert_related_videos">संबंधित व्हिडिओ समाविष्ट करा</string>
|
||||||
<string name="brightness">ब्राईटनेस</string>
|
<string name="brightness">ब्राईटनेस</string>
|
||||||
|
@ -308,7 +308,6 @@
|
|||||||
<string name="notification_time_summary">Tidsperiode merknadene kan vises.</string>
|
<string name="notification_time_summary">Tidsperiode merknadene kan vises.</string>
|
||||||
<string name="navbar_order">Rekkefølge</string>
|
<string name="navbar_order">Rekkefølge</string>
|
||||||
<string name="alternative_player_layout_summary">Vis relaterte videoer som rad over kommentarene istedenfor under.</string>
|
<string name="alternative_player_layout_summary">Vis relaterte videoer som rad over kommentarene istedenfor under.</string>
|
||||||
<string name="default_audio_track">Forvalg</string>
|
|
||||||
<string name="audio_track">Lydspor</string>
|
<string name="audio_track">Lydspor</string>
|
||||||
<string name="hls_instead_of_dash">Bruk HLS</string>
|
<string name="hls_instead_of_dash">Bruk HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Bruk HLS istedenfor DASH (tregere og ikke anbefalt)</string>
|
<string name="hls_instead_of_dash_summary">Bruk HLS istedenfor DASH (tregere og ikke anbefalt)</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout">ବିକଳ୍ପ ପ୍ଲେୟାର୍ ଲେଆଉଟ୍</string>
|
<string name="alternative_player_layout">ବିକଳ୍ପ ପ୍ଲେୟାର୍ ଲେଆଉଟ୍</string>
|
||||||
<string name="alternative_player_layout_summary">ନିମ୍ନୋକ୍ତ ମନ୍ତବ୍ୟଗୁଡିକ ଉପରେ ସଂପୃକ୍ତ ଭିଡିଓଗୁଡିକ ଧାଡି ଭାବରେ ଦେଖାନ୍ତୁ ।</string>
|
<string name="alternative_player_layout_summary">ନିମ୍ନୋକ୍ତ ମନ୍ତବ୍ୟଗୁଡିକ ଉପରେ ସଂପୃକ୍ତ ଭିଡିଓଗୁଡିକ ଧାଡି ଭାବରେ ଦେଖାନ୍ତୁ ।</string>
|
||||||
<string name="audio_track">ଅଡିଓ ଟ୍ରାକ୍</string>
|
<string name="audio_track">ଅଡିଓ ଟ୍ରାକ୍</string>
|
||||||
<string name="default_audio_track">ଡିଫଲ୍ଟ</string>
|
|
||||||
<string name="hls_instead_of_dash">HLS ବ୍ୟବହାର କରନ୍ତୁ</string>
|
<string name="hls_instead_of_dash">HLS ବ୍ୟବହାର କରନ୍ତୁ</string>
|
||||||
<string name="hls_instead_of_dash_summary">DASH ପରିବର୍ତ୍ତେ HLS ବ୍ୟବହାର କରନ୍ତୁ (ଧୀର ହେବ, ସୁପାରିଶ ହେବ ନାହିଁ)</string>
|
<string name="hls_instead_of_dash_summary">DASH ପରିବର୍ତ୍ତେ HLS ବ୍ୟବହାର କରନ୍ତୁ (ଧୀର ହେବ, ସୁପାରିଶ ହେବ ନାହିଁ)</string>
|
||||||
<string name="auto_quality">ସ୍ଵତଃ</string>
|
<string name="auto_quality">ସ୍ଵତଃ</string>
|
||||||
|
@ -336,7 +336,6 @@
|
|||||||
<string name="layout">ਲੇਆਊਟ</string>
|
<string name="layout">ਲੇਆਊਟ</string>
|
||||||
<string name="notification_time_summary">ਸਮਾਂ ਸੀਮਤ ਕਰੋ ਜਿਸ ਵਿੱਚ ਸਟ੍ਰੀਮ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ।</string>
|
<string name="notification_time_summary">ਸਮਾਂ ਸੀਮਤ ਕਰੋ ਜਿਸ ਵਿੱਚ ਸਟ੍ਰੀਮ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ।</string>
|
||||||
<string name="navbar_order">ਆਰਡਰ</string>
|
<string name="navbar_order">ਆਰਡਰ</string>
|
||||||
<string name="default_audio_track">ਡੀਫ਼ਾਲਟ</string>
|
|
||||||
<string name="alternative_player_layout">ਵਿਕਲਪਿਕ ਪਲੇਅਰ ਲੇਆਊਟ</string>
|
<string name="alternative_player_layout">ਵਿਕਲਪਿਕ ਪਲੇਅਰ ਲੇਆਊਟ</string>
|
||||||
<string name="trending">ਹੁਣ ਕੀ ਰੁਝਾਨ ਹੈ</string>
|
<string name="trending">ਹੁਣ ਕੀ ਰੁਝਾਨ ਹੈ</string>
|
||||||
<string name="hls_instead_of_dash">HLS ਦੀ ਵਰਤੋਂ ਕਰੋ</string>
|
<string name="hls_instead_of_dash">HLS ਦੀ ਵਰਤੋਂ ਕਰੋ</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout_summary">Pokaż powiązane filmiki w wierszu nad komentarzami.</string>
|
<string name="alternative_player_layout_summary">Pokaż powiązane filmiki w wierszu nad komentarzami.</string>
|
||||||
<string name="alternative_player_layout">Inny układ powiązanych filmów</string>
|
<string name="alternative_player_layout">Inny układ powiązanych filmów</string>
|
||||||
<string name="audio_track">Ścieżka dźwiękowa</string>
|
<string name="audio_track">Ścieżka dźwiękowa</string>
|
||||||
<string name="default_audio_track">Domyślna</string>
|
|
||||||
<string name="hls_instead_of_dash">HTTP Live Streaming</string>
|
<string name="hls_instead_of_dash">HTTP Live Streaming</string>
|
||||||
<string name="hls_instead_of_dash_summary">Użyj HLS zamiast DASH (będzie wolniej, nie zalecane)</string>
|
<string name="hls_instead_of_dash_summary">Użyj HLS zamiast DASH (będzie wolniej, nie zalecane)</string>
|
||||||
<string name="auto_quality">Automatyczna</string>
|
<string name="auto_quality">Automatyczna</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout">Esquema alternativo do reprodutor</string>
|
<string name="alternative_player_layout">Esquema alternativo do reprodutor</string>
|
||||||
<string name="alternative_player_layout_summary">Exibir os vídeos relacionados como uma linha acima dos comentários em vez de abaixo.</string>
|
<string name="alternative_player_layout_summary">Exibir os vídeos relacionados como uma linha acima dos comentários em vez de abaixo.</string>
|
||||||
<string name="audio_track">Faixa de áudio</string>
|
<string name="audio_track">Faixa de áudio</string>
|
||||||
<string name="default_audio_track">Padrão</string>
|
|
||||||
<string name="hls_instead_of_dash">Usar HLS</string>
|
<string name="hls_instead_of_dash">Usar HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Usar HLS em vez de DASH (será mais lento, não recomendado)</string>
|
<string name="hls_instead_of_dash_summary">Usar HLS em vez de DASH (será mais lento, não recomendado)</string>
|
||||||
<string name="auto_quality">Automático</string>
|
<string name="auto_quality">Automático</string>
|
||||||
|
@ -312,7 +312,6 @@
|
|||||||
<string name="alternative_player_layout">Esquema alternativo de reprodução</string>
|
<string name="alternative_player_layout">Esquema alternativo de reprodução</string>
|
||||||
<string name="alternative_player_layout_summary">Mostrar os vídeos relacionados como uma linha acima dos comentários em vez de abaixo.</string>
|
<string name="alternative_player_layout_summary">Mostrar os vídeos relacionados como uma linha acima dos comentários em vez de abaixo.</string>
|
||||||
<string name="audio_track">Faixa de áudio</string>
|
<string name="audio_track">Faixa de áudio</string>
|
||||||
<string name="default_audio_track">Padrão</string>
|
|
||||||
<string name="hls_instead_of_dash">Usar HLS</string>
|
<string name="hls_instead_of_dash">Usar HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Usar HLS em vez de DASH (será mais lento, não recomendado)</string>
|
<string name="hls_instead_of_dash_summary">Usar HLS em vez de DASH (será mais lento, não recomendado)</string>
|
||||||
<string name="auto_quality">Automático</string>
|
<string name="auto_quality">Automático</string>
|
||||||
|
@ -339,7 +339,6 @@
|
|||||||
<string name="start_time">Timpul de începere</string>
|
<string name="start_time">Timpul de începere</string>
|
||||||
<string name="end_time">Timpul de sfârșit</string>
|
<string name="end_time">Timpul de sfârșit</string>
|
||||||
<string name="notification_time">Restricționați timpul de notificare</string>
|
<string name="notification_time">Restricționați timpul de notificare</string>
|
||||||
<string name="default_audio_track">Implicit</string>
|
|
||||||
<string name="unsupported_file_format">Format de fișier nesuportat: %1$s</string>
|
<string name="unsupported_file_format">Format de fișier nesuportat: %1$s</string>
|
||||||
<string name="hls_instead_of_dash">Utilizați HLS</string>
|
<string name="hls_instead_of_dash">Utilizați HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Utilizați HLS în loc de DASH (va fi mai lent, nu este recomandat)</string>
|
<string name="hls_instead_of_dash_summary">Utilizați HLS în loc de DASH (va fi mai lent, nu este recomandat)</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="layout">Макет</string>
|
<string name="layout">Макет</string>
|
||||||
<string name="alternative_player_layout">Альтернативный макет плеера</string>
|
<string name="alternative_player_layout">Альтернативный макет плеера</string>
|
||||||
<string name="audio_track">Аудио трек</string>
|
<string name="audio_track">Аудио трек</string>
|
||||||
<string name="default_audio_track">По умолчанию</string>
|
|
||||||
<string name="hls_instead_of_dash">Использовать HLS</string>
|
<string name="hls_instead_of_dash">Использовать HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Использовать HLS вместо DASH (будет медленнее, не рекомендуется)</string>
|
<string name="hls_instead_of_dash_summary">Использовать HLS вместо DASH (будет медленнее, не рекомендуется)</string>
|
||||||
<string name="auto_quality">Авто</string>
|
<string name="auto_quality">Авто</string>
|
||||||
|
@ -223,7 +223,6 @@
|
|||||||
<string name="navbar_order">පිළිවෙල</string>
|
<string name="navbar_order">පිළිවෙල</string>
|
||||||
<string name="layout">පිරිසැලසුම</string>
|
<string name="layout">පිරිසැලසුම</string>
|
||||||
<string name="audio_track">ශ්රව්ය පථය</string>
|
<string name="audio_track">ශ්රව්ය පථය</string>
|
||||||
<string name="default_audio_track">පෙරනිමිය</string>
|
|
||||||
<string name="hls_instead_of_dash">HLS භාවිතා කරන්න</string>
|
<string name="hls_instead_of_dash">HLS භාවිතා කරන්න</string>
|
||||||
<string name="auto_quality">ස්වයං</string>
|
<string name="auto_quality">ස්වයං</string>
|
||||||
<string name="limit_to_runtime">ධාවන කාලයට සීමා කරන්න</string>
|
<string name="limit_to_runtime">ධාවන කාලයට සීමා කරන්න</string>
|
||||||
|
@ -308,7 +308,6 @@
|
|||||||
<string name="sb_markers">Маркери</string>
|
<string name="sb_markers">Маркери</string>
|
||||||
<string name="sb_markers_summary">Означите сегменте на временској траци.</string>
|
<string name="sb_markers_summary">Означите сегменте на временској траци.</string>
|
||||||
<string name="audio_track">Аудио запис</string>
|
<string name="audio_track">Аудио запис</string>
|
||||||
<string name="default_audio_track">Подразумевано</string>
|
|
||||||
<string name="livestreams">Стримови уживо</string>
|
<string name="livestreams">Стримови уживо</string>
|
||||||
<string name="alternative_videos_layout">Алтернативни изглед видео записа</string>
|
<string name="alternative_videos_layout">Алтернативни изглед видео записа</string>
|
||||||
<string name="defaultIconLight">Подразумевано светло</string>
|
<string name="defaultIconLight">Подразумевано светло</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout_summary">İlgili videoları, yorumların altında değil, üstünde bir satır olarak göster.</string>
|
<string name="alternative_player_layout_summary">İlgili videoları, yorumların altında değil, üstünde bir satır olarak göster.</string>
|
||||||
<string name="navbar_order">Sıra</string>
|
<string name="navbar_order">Sıra</string>
|
||||||
<string name="audio_track">Müzik parçası</string>
|
<string name="audio_track">Müzik parçası</string>
|
||||||
<string name="default_audio_track">Varsayılan</string>
|
|
||||||
<string name="auto_quality">Otomatik</string>
|
<string name="auto_quality">Otomatik</string>
|
||||||
<string name="hls_instead_of_dash">HLS\'yi kullan</string>
|
<string name="hls_instead_of_dash">HLS\'yi kullan</string>
|
||||||
<string name="hls_instead_of_dash_summary">DASH yerine HLS kullan (daha yavaş olacaktır, önerilmez)</string>
|
<string name="hls_instead_of_dash_summary">DASH yerine HLS kullan (daha yavaş olacaktır, önerilmez)</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="layout">Вигляд</string>
|
<string name="layout">Вигляд</string>
|
||||||
<string name="alternative_player_layout">Альтернативний вигляд програвача</string>
|
<string name="alternative_player_layout">Альтернативний вигляд програвача</string>
|
||||||
<string name="audio_track">Звукова доріжка</string>
|
<string name="audio_track">Звукова доріжка</string>
|
||||||
<string name="default_audio_track">Типово</string>
|
|
||||||
<string name="hls_instead_of_dash">Використовувати HLS</string>
|
<string name="hls_instead_of_dash">Використовувати HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Використовувати HLS замість DASH (працюватиме повільніше, не рекомендується)</string>
|
<string name="hls_instead_of_dash_summary">Використовувати HLS замість DASH (працюватиме повільніше, не рекомендується)</string>
|
||||||
<string name="auto_quality">Авто</string>
|
<string name="auto_quality">Авто</string>
|
||||||
|
@ -310,7 +310,6 @@
|
|||||||
<string name="sb_markers_summary">Đánh dấu các phân đoạn trên thanh thời lượng.</string>
|
<string name="sb_markers_summary">Đánh dấu các phân đoạn trên thanh thời lượng.</string>
|
||||||
<string name="sb_markers">Đánh dấu</string>
|
<string name="sb_markers">Đánh dấu</string>
|
||||||
<string name="audio_track">Track âm thanh</string>
|
<string name="audio_track">Track âm thanh</string>
|
||||||
<string name="default_audio_track">Mặc định</string>
|
|
||||||
<string name="livestreams">Trực tiếp</string>
|
<string name="livestreams">Trực tiếp</string>
|
||||||
<string name="alternative_videos_layout">Bố cục thay thế cho videos</string>
|
<string name="alternative_videos_layout">Bố cục thay thế cho videos</string>
|
||||||
<string name="defaultIconLight">Light mặc định</string>
|
<string name="defaultIconLight">Light mặc định</string>
|
||||||
|
@ -311,7 +311,6 @@
|
|||||||
<string name="alternative_player_layout">备选的播放器布局</string>
|
<string name="alternative_player_layout">备选的播放器布局</string>
|
||||||
<string name="alternative_player_layout_summary">将相关视频显示为评论上方而不是下方的一行。</string>
|
<string name="alternative_player_layout_summary">将相关视频显示为评论上方而不是下方的一行。</string>
|
||||||
<string name="audio_track">音轨</string>
|
<string name="audio_track">音轨</string>
|
||||||
<string name="default_audio_track">默认</string>
|
|
||||||
<string name="hls_instead_of_dash">使用 HLS</string>
|
<string name="hls_instead_of_dash">使用 HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">使用 HLS 而不是 DASH(会更慢,不推荐)</string>
|
<string name="hls_instead_of_dash_summary">使用 HLS 而不是 DASH(会更慢,不推荐)</string>
|
||||||
<string name="auto_quality">自动</string>
|
<string name="auto_quality">自动</string>
|
||||||
|
@ -327,7 +327,6 @@
|
|||||||
<string name="pinch_control">調整音高</string>
|
<string name="pinch_control">調整音高</string>
|
||||||
<string name="play_latest_videos">播放最新影片</string>
|
<string name="play_latest_videos">播放最新影片</string>
|
||||||
<string name="audio_track">音訊曲目</string>
|
<string name="audio_track">音訊曲目</string>
|
||||||
<string name="default_audio_track">預設</string>
|
|
||||||
<string name="export_subscriptions">匯出訂閱列表</string>
|
<string name="export_subscriptions">匯出訂閱列表</string>
|
||||||
<string name="skip_buttons">跳過按鈕</string>
|
<string name="skip_buttons">跳過按鈕</string>
|
||||||
<string name="no_search_result">無結果。</string>
|
<string name="no_search_result">無結果。</string>
|
||||||
|
@ -330,7 +330,6 @@
|
|||||||
<string name="alternative_player_layout">Alternative player layout</string>
|
<string name="alternative_player_layout">Alternative player layout</string>
|
||||||
<string name="alternative_player_layout_summary">Show the related videos as a row above the comments instead of below.</string>
|
<string name="alternative_player_layout_summary">Show the related videos as a row above the comments instead of below.</string>
|
||||||
<string name="audio_track">Audio track</string>
|
<string name="audio_track">Audio track</string>
|
||||||
<string name="default_audio_track">Default</string>
|
|
||||||
<string name="unsupported_file_format">Unsupported file format: %1$s</string>
|
<string name="unsupported_file_format">Unsupported file format: %1$s</string>
|
||||||
<string name="hls_instead_of_dash">Use HLS</string>
|
<string name="hls_instead_of_dash">Use HLS</string>
|
||||||
<string name="hls_instead_of_dash_summary">Use HLS instead of DASH (will be slower, not recommended)</string>
|
<string name="hls_instead_of_dash_summary">Use HLS instead of DASH (will be slower, not recommended)</string>
|
||||||
@ -445,6 +444,15 @@
|
|||||||
<string name="import_format_youtube_csv">YouTube (CSV)</string>
|
<string name="import_format_youtube_csv">YouTube (CSV)</string>
|
||||||
<string name="home_tab_content">Home tab content</string>
|
<string name="home_tab_content">Home tab content</string>
|
||||||
<string name="show_search_suggestions">Show search suggestions</string>
|
<string name="show_search_suggestions">Show search suggestions</string>
|
||||||
|
<string name="audio_track_format">%1$s - %2$s</string>
|
||||||
|
<string name="unknown_audio_language">Unknown audio language</string>
|
||||||
|
<string name="unknown_audio_track_type">Unknown audio track type</string>
|
||||||
|
<string name="original_or_main_audio_track">original or main</string>
|
||||||
|
<string name="dubbed_audio_track">dubbed</string>
|
||||||
|
<string name="descriptive_audio_track">descriptive</string>
|
||||||
|
<string name="default_or_unknown_audio_track">default or unknown</string>
|
||||||
|
<string name="unknown_or_no_audio">unknown or no audio</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
<string name="download_channel_description">Shows a notification when downloading media.</string>
|
<string name="download_channel_description">Shows a notification when downloading media.</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user