Compare commits

...

53 Commits

Author SHA1 Message Date
Sofie 93d81ea038
WebViewResolver: added timeout (#941) 2024-02-19 21:18:36 +01:00
Sofie e007714701
fix Rabbitstream (#936)
* fix Rabbitstream

* .
2024-02-19 21:06:55 +01:00
KingLucius 805f80b2ac
Long press Repo to copy URL (#934) 2024-02-19 16:46:02 +01:00
KingLucius b5fb0997c4
[TV] Limit Homepage Header Description to 3 lines & 6 Tags (#938) 2024-02-19 16:44:50 +01:00
KingLucius ca918b1581
[TV] More space around result description (#939) 2024-02-19 16:43:41 +01:00
IndusAryan 09779b4ee0
chore: add tooltips on results toolbar and rename speed mode (#925)
* add tooltips on results toolbar and better summaries, rename speed mode

* remove redundant space
2024-02-15 21:45:34 +01:00
Sofie 012d38398e
fix Acefile & Gofile (#926) 2024-02-15 21:42:47 +01:00
Ömer Faruk Sancak d1db4c3370
Extractor: Added PlayRu (#930) 2024-02-15 21:42:11 +01:00
Sarlay 8d318ca84a
Fixed Chillx subtitles (#931)
* Fixed subtitles tracks for Chillx.kt

* Update Chillx.kt
2024-02-15 21:41:34 +01:00
KingLucius eea6e13346
Make Rating non-focusable (Old API) (#935) 2024-02-15 21:40:44 +01:00
CranberrySoup 2b7d102716
Add SubScene (#923)
* Lower targetSdk to get all installed packages

* Update sdk version

* Let's not be too radical

* Many fixes

* Revert targetSdk

* Make account homepage persistent

* Add SubScene and change subtitle API

Co-authored-by: Aymanbest <51868001+aymanbest@users.noreply.github.com>

* Fix file deletion

---------

Co-authored-by: Aymanbest <51868001+aymanbest@users.noreply.github.com>
2024-02-06 23:27:35 +01:00
Osten 9ea7674a0f
Update MainAPI.kt 2024-02-02 22:18:54 +01:00
IndusAryan 3dcf7076d0
feat(ui): tap video duration to toggle remaining time counter (#878) 2024-01-21 20:11:51 +01:00
Sofie 8b14fcb881
added Mediafire (#906) 2024-01-21 15:35:33 +01:00
IndusAryan 01f21e0fe8
refactor: move buildconfig, bump ksp & better trailer scraping (#834) 2024-01-19 21:09:07 +01:00
coxju bdef6524e7
feat : run custom js in webviewresolver (#888)
Co-authored-by: coxju <coxju>
2024-01-19 20:38:37 +01:00
Cloudburst f40a8d9418
make *rotate_video_key untransatable (#896) 2024-01-19 20:12:52 +01:00
IndusAryan 03fcb106ac
new simple messages when updating app i.e, refined changelogs (#900) 2024-01-19 20:12:33 +01:00
coxju 636e157c63
fix: trailers not playing (#898)
Co-authored-by: coxju <coxju>
2024-01-19 12:03:20 +01:00
CranberrySoup 5af1b80cb7
Fix crash on plugin reload (#895)
* Update Event.kt

* Update Event.kt
2024-01-18 22:57:54 +01:00
coxju 5dfc08aabb
feat: added emturbovid extractor (#893)
Co-authored-by: coxju <coxju>
2024-01-17 23:28:17 +01:00
coxju 1676094488
feat (loadExtractor) : match mirror domains of extractor link (#877)
Co-authored-by: coxju <coxju>
2024-01-17 22:32:22 +01:00
Sir Aguacata 19145c6cc4
Screw it, Self host keys (#892) 2024-01-17 22:30:20 +01:00
coxju ebb72d6a0c
feat : invalidate link cache after 20 mins (#875)
- additionaly clear cache if there is player errors or no links found

Co-authored-by: coxju <coxju>
2024-01-17 22:29:44 +01:00
Sir Aguacata 399b28c75b
Rest in piece old key repo (#891) 2024-01-17 00:35:23 +01:00
IndusAryan 601483e103
feat: limit genre tags on home to 2 lines and on result page, 10 tags max (#885) 2024-01-16 18:41:43 +01:00
recloudstream[bot] 9733d0b316 chore(locales): fix locale issues 2024-01-16 17:40:50 +00:00
Weblate (bot) 0cf199248a
Translated using Weblate (Croatian) (#856)
Currently translated at 100.0% (4 of 4 strings)

Translated using Weblate (Russian)

Currently translated at 94.7% (634 of 669 strings)

Translated using Weblate (Croatian)

Currently translated at 75.0% (3 of 4 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (669 of 669 strings)

Merge remote-tracking branch 'origin/master'

Translated using Weblate (Arabic (Levantine))

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (German)

Currently translated at 99.1% (663 of 669 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 90.2% (603 of 668 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (668 of 668 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (668 of 668 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (668 of 668 strings)

Translated using Weblate (Japanese)

Currently translated at 49.8% (333 of 668 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 96.5% (645 of 668 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (668 of 668 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (668 of 668 strings)

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Translated using Weblate (Swedish)

Currently translated at 100.0% (4 of 4 strings)

Translated using Weblate (Japanese)

Currently translated at 47.3% (316 of 668 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (668 of 668 strings)

Translated using Weblate (Swedish)

Currently translated at 99.4% (664 of 668 strings)

Translated using Weblate (Nepali)

Currently translated at 26.9% (180 of 668 strings)

Translated using Weblate (German)

Currently translated at 99.1% (662 of 668 strings)

Merge remote-tracking branch 'origin/master'

Translated using Weblate (Odia)

Currently translated at 38.3% (256 of 668 strings)

Translated using Weblate (Croatian)

Currently translated at 99.7% (666 of 668 strings)

Translated using Weblate (Nepali)

Currently translated at 16.9% (113 of 668 strings)

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Translated using Weblate (Polish)

Currently translated at 99.7% (666 of 668 strings)

Translated using Weblate (Malayalam)

Currently translated at 45.2% (302 of 668 strings)

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Translated using Weblate (Arabic)

Currently translated at 100.0% (668 of 668 strings)

Translated using Weblate (Bengali)

Currently translated at 48.5% (324 of 668 strings)

Translated using Weblate (Bengali)

Currently translated at 48.5% (324 of 668 strings)

Translated using Weblate (Persian)

Currently translated at 27.6% (185 of 668 strings)

Translated using Weblate (Persian)

Currently translated at 27.6% (185 of 668 strings)

Translated using Weblate (Bengali)

Currently translated at 36.8% (246 of 668 strings)

Translated using Weblate (Tagalog)

Currently translated at 51.9% (347 of 668 strings)

Translated using Weblate (Swedish)

Currently translated at 78.8% (527 of 668 strings)

Translated using Weblate (Malayalam)

Currently translated at 39.0% (261 of 668 strings)

Translated using Weblate (Arabic (Levantine))

Currently translated at 100.0% (4 of 4 strings)

Translated using Weblate (Polish)

Currently translated at 98.6% (659 of 668 strings)























Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/apc/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ar/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/bn/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/cs/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/de/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/es/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/fa/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/hr/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ja/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ml/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ne/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/or/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ru/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sv/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/tl/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/tr/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/apc/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/hr/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/sv/
Translation: Cloudstream/App
Translation: Cloudstream/Fastlane

Co-authored-by: Aayush Shah <shahaayush999@gmail.com>
Co-authored-by: Alexander Svärd <genc.demiri@hotmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Amir <amearb@duck.com>
Co-authored-by: Anarchydr <patrikpik879@gmail.com>
Co-authored-by: Cloudburst <18114966+C10udburst@users.noreply.github.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Hubert Naciasta <hubert.naciasta@skiff.com>
Co-authored-by: IamNotNickerson <IamNickerson@users.noreply.hosted.weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Ovi329 <avijitb129@gmail.com>
Co-authored-by: Pizza Party <paol.m@proton.me>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Slawa <slawa@slawagurevich.com>
Co-authored-by: Subham Jena <subhamjena8465@gmail.com>
Co-authored-by: dabao1955 <dabao1955@163.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Co-authored-by: Ömer Faruk Sancak <keyiflerolsun@gmail.com>
Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com>
2024-01-16 18:40:37 +01:00
Sofie 2624947b5b
fix keys (#887)
Co-authored-by: ghost <ghost@gmail.com>
2024-01-16 18:37:31 +01:00
coxju 31c783d0b4
feat: added extractor vidhide and streamwish (#889)
Co-authored-by: coxju <coxju>
2024-01-16 18:36:02 +01:00
firelight 9f1b172f34
fix plugin downloads trash can 2024-01-14 01:06:06 +01:00
IndusAryan 93dce8682e
fix system bars not appearing properly (#879) 2024-01-13 22:45:30 +01:00
IndusAryan 723c653b07
feat(ui): long press title to copy (#872)
* new feature: hold to copy movie title

* remove title copy hint
2024-01-12 16:48:43 +01:00
coxju 0c73f5e59a
fix: library not loading in TV (#871) 2024-01-11 17:08:37 +01:00
coxju 0eb152c5db
fix: search only if selection changed (#868) 2024-01-11 15:54:28 +01:00
IndusAryan 8c5ab86714
hotfix window compat bug (#870) 2024-01-11 15:53:31 +01:00
Ömer Faruk Sancak 85a769a898
Extractor: ContentX add external subtitle (#869) 2024-01-11 15:52:34 +01:00
Sir Aguacata 96aa56209b
Revert the repo change to get keys (#867) 2024-01-11 00:47:40 +01:00
coxju d71d3890b5
feat: show random button on library (#855) 2024-01-10 22:28:06 +01:00
IndusAryan 19b1a40cf8
use window insets compat controller (#847) 2024-01-10 22:20:43 +01:00
Yutatsu e5f483b0b2
Fix vidplay extractor (#866) 2024-01-10 22:06:45 +01:00
coxju 6f1e0bef80
feat: show the episodic range with current selection checked (#851) 2024-01-10 22:05:56 +01:00
LagradOst 5e6272be3f fix 2024-01-10 19:10:34 +01:00
coxju 97ec98b9e2
feat : show favorite button in bottom dialog (#858)
Co-authored-by: coxju <coxju>
2024-01-10 18:55:10 +01:00
coxju 42fd0b5c76
new streamtape extractor (#857) 2024-01-08 23:46:19 +01:00
Ömer Faruk Sancak 42774f6183
Extractor: ContentX expanded and fix (#859)
* Extractor: ContentX expanded and fix
2024-01-08 23:45:53 +01:00
Sofie f687508521
Extractors: fix acefile, pixeldrain & vidplay tracks (#854)
Co-authored-by: ghost <ghost@gmail.com>
2024-01-05 17:08:48 +01:00
recloudstream[bot] dbba6d7f27 chore(locales): fix locale issues 2024-01-05 16:08:37 +00:00
Hosted Weblate f4da170a57 Translated using Weblate (Polish)
Currently translated at 99.7% (666 of 668 strings)

Translated using Weblate (Malayalam)

Currently translated at 45.2% (302 of 668 strings)

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Merge remote-tracking branch 'origin/master'

Translated using Weblate (Arabic)

Currently translated at 100.0% (668 of 668 strings)

Translated using Weblate (Bengali)

Currently translated at 48.5% (324 of 668 strings)

Translated using Weblate (Bengali)

Currently translated at 48.5% (324 of 668 strings)

Translated using Weblate (Persian)

Currently translated at 27.6% (185 of 668 strings)

Translated using Weblate (Persian)

Currently translated at 27.6% (185 of 668 strings)

Translated using Weblate (Bengali)

Currently translated at 36.8% (246 of 668 strings)

Translated using Weblate (Tagalog)

Currently translated at 51.9% (347 of 668 strings)

Translated using Weblate (Swedish)

Currently translated at 78.8% (527 of 668 strings)

Translated using Weblate (Malayalam)

Currently translated at 39.0% (261 of 668 strings)

Translated using Weblate (Arabic (Levantine))

Currently translated at 100.0% (4 of 4 strings)

Translated using Weblate (Polish)

Currently translated at 98.6% (659 of 668 strings)

Co-authored-by: Amir <amearb@duck.com>
Co-authored-by: Cloudburst <18114966+C10udburst@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hubert Naciasta <hubert.naciasta@skiff.com>
Co-authored-by: IamNotNickerson <IamNickerson@users.noreply.hosted.weblate.org>
Co-authored-by: Ovi329 <avijitb129@gmail.com>
Co-authored-by: Pizza Party <paol.m@proton.me>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ar/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/bn/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/fa/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ml/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sv/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/tl/
Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/apc/
Translation: Cloudstream/App
Translation: Cloudstream/Fastlane
2024-01-05 17:08:20 +01:00
Cloudburst 2a1876f54c
fix build_to_archive.yml 2024-01-03 10:28:39 +01:00
wrongwrong f1d0a8e955
Fixed to not use API that will be discontinued in the future (#843) 2024-01-03 10:24:42 +01:00
IndusAryan 1c6be2d5cb
refactor(bump): upgrade workflow versions to use node16 (#793) 2024-01-03 10:24:28 +01:00
Horis fc802cdcdd
add extractors (#849) 2024-01-03 10:24:12 +01:00
93 changed files with 1847 additions and 454 deletions

View File

@ -19,21 +19,21 @@ jobs:
steps:
- name: Generate access token
id: generate_token
uses: tibdex/github-app-token@v1
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
repository: "recloudstream/secrets"
- name: Generate access token (archive)
id: generate_archive_token
uses: tibdex/github-app-token@v1
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
repository: "recloudstream/cloudstream-archive"
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v2
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt'
@ -58,7 +58,7 @@ jobs:
SIGNING_STORE_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
SIMKL_CLIENT_ID: ${{ secrets.SIMKL_CLIENT_ID }}
SIMKL_CLIENT_SECRET: ${{ secrets.SIMKL_CLIENT_SECRET }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
repository: "recloudstream/cloudstream-archive"
token: ${{ steps.generate_archive_token.outputs.token }}

View File

@ -20,7 +20,7 @@ jobs:
steps:
- name: Generate access token
id: generate_token
uses: tibdex/github-app-token@v1
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
@ -43,12 +43,13 @@ jobs:
rm -rf "./-cloudstream"
- name: Setup JDK 17
uses: actions/setup-java@v1
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'adopt'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
uses: android-actions/setup-android@v3
- name: Generate Dokka
run: |

View File

@ -10,7 +10,7 @@ jobs:
steps:
- name: Generate access token
id: generate_token
uses: tibdex/github-app-token@v1
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
@ -27,7 +27,7 @@ jobs:
comment-body: '${index}. ${similarity} #${number}'
- name: Label if possible duplicate
if: steps.similarity.outputs.similar-issues-found =='true'
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
github-token: ${{ steps.generate_token.outputs.token }}
script: |
@ -37,7 +37,7 @@ jobs:
repo: context.repo.repo,
labels: ["possible duplicate"]
})
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Automatically close issues that dont follow the issue template
uses: lucasbento/auto-close-issues@v1.0.2
with:
@ -68,7 +68,7 @@ jobs:
Found provider name: `${{ steps.provider_check.outputs.name }}`
- name: Label if mentions provider
if: steps.provider_check.outputs.name != 'none'
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
github-token: ${{ steps.generate_token.outputs.token }}
script: |

View File

@ -18,14 +18,14 @@ jobs:
steps:
- name: Generate access token
id: generate_token
uses: tibdex/github-app-token@v1
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
repository: "recloudstream/secrets"
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v2
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt'

View File

@ -6,9 +6,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v2
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt'
@ -17,7 +17,7 @@ jobs:
- name: Run Gradle
run: ./gradlew assemblePrereleaseDebug
- name: Upload Artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: pull-request-build
path: "app/build/outputs/apk/prerelease/debug/*.apk"

View File

@ -18,12 +18,12 @@ jobs:
steps:
- name: Generate access token
id: generate_token
uses: tibdex/github-app-token@v1
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
repository: "recloudstream/cloudstream"
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
token: ${{ steps.generate_token.outputs.token }}
- name: Install dependencies

View File

@ -139,6 +139,10 @@ android {
abortOnError = false
checkReleaseBuilds = false
}
buildFeatures {
buildConfig = true
}
namespace = "com.lagradost.cloudstream3"
}
@ -159,10 +163,10 @@ dependencies {
// Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
// Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0")
@ -195,13 +199,13 @@ dependencies {
// PlayBack
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
implementation("com.github.teamnewpipe:NewPipeExtractor:eac850") /* For Trailers
implementation("com.github.teamnewpipe:NewPipeExtractor:6dc25f7") /* For Trailers
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding
// Crash Reports (AcraApplication.kt)
implementation("ch.acra:acra-core:5.11.2")
implementation("ch.acra:acra-toast:5.11.2")
implementation("ch.acra:acra-core:5.11.3")
implementation("ch.acra:acra-toast:5.11.3")
// UI Stuff
implementation("com.facebook.shimmer:shimmer:0.5.0") // Shimmering Effect (Loading Skeleton)
@ -224,9 +228,9 @@ dependencies {
Level 25 or Less. */
// Downloading & Networking
implementation("androidx.work:work-runtime:2.8.1")
implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("com.github.Blatzar:NiceHttp:0.4.4") // HTTP Lib
implementation("androidx.work:work-runtime:2.9.0")
implementation("androidx.work:work-runtime-ktx:2.9.0")
implementation("com.github.Blatzar:NiceHttp:0.4.5") // HTTP Lib
}
tasks.register("androidSourcesJar", Jar::class) {

View File

@ -9,7 +9,7 @@ import androidx.preference.PreferenceManager
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
@ -34,7 +34,7 @@ const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
val mapper = JsonMapper.builder().addModule(KotlinModule())
val mapper = JsonMapper.builder().addModule(kotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
/**
@ -1587,8 +1587,15 @@ data class AnimeLoadResponse(
}
override fun getTotalEpisodeIndex(episode: Int, season: Int): Int {
val displayMap = this.seasonNames?.associate { it.season to it.displaySeason } ?: emptyMap()
return this.episodes.maxOf { (_, episodes) ->
episodes.count { ((it.season ?: Int.MIN_VALUE) < season) && it.season != 0 }
episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season.
episodeSeason in 1..<season
}
} + episode
}
@ -1895,8 +1902,13 @@ data class TvSeriesLoadResponse(
}
override fun getTotalEpisodeIndex(episode: Int, season: Int): Int {
return episodes.count {
(it.season ?: Int.MIN_VALUE) < season && it.season != 0
val displayMap = this.seasonNames?.associate { it.season to it.displaySeason } ?: emptyMap()
return episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season.
episodeSeason in 1..<season
} + episode
}

View File

@ -61,6 +61,7 @@ import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.initAll
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.loadThemes
@ -287,9 +288,27 @@ var app = Requests(responseParser = object : ResponseParser {
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
companion object {
const val TAG = "MAINACT"
const val ANIMATED_OUTLINE : Boolean = false
const val ANIMATED_OUTLINE: Boolean = false
var lastError: String? = null
private const val FILE_DELETE_KEY = "FILES_TO_DELETE_KEY"
/**
* Transient files to delete on application exit.
* Deletes files on onDestroy().
*/
private var filesToDelete: Set<String>
// This needs to be persistent because the application may exit without calling onDestroy.
get() = getKey<Set<String>>(FILE_DELETE_KEY) ?: setOf()
private set(value) = setKey(FILE_DELETE_KEY, value)
/**
* Add file to delete on Exit.
*/
fun deleteFileOnExit(file: File) {
filesToDelete = filesToDelete + file.path
}
/**
* Setting this will automatically enter the query in the search
* next time the search fragment is opened.
@ -676,6 +695,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
override fun onDestroy() {
filesToDelete.forEach { path ->
val result = File(path).deleteRecursively()
if (result) {
Log.d(TAG, "Deleted temporary file: $path")
} else {
Log.d(TAG, "Failed to delete temporary file: $path")
}
}
filesToDelete = setOf()
val broadcastIntent = Intent()
broadcastIntent.action = "restart_service"
broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java)
@ -1374,6 +1402,35 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
observeNullable(viewModel.favoriteStatus) observeFavoriteStatus@{ isFavorite ->
resultviewPreviewFavorite.isVisible = isFavorite != null
if (isFavorite == null) return@observeFavoriteStatus
val drawable = if (isFavorite) {
R.drawable.ic_baseline_favorite_24
} else {
R.drawable.ic_baseline_favorite_border_24
}
resultviewPreviewFavorite.setImageResource(drawable)
}
resultviewPreviewFavorite.setOnClickListener{
viewModel.toggleFavoriteStatus(this@MainActivity) { newStatus: Boolean? ->
if (newStatus == null) return@toggleFavoriteStatus
val message = if (newStatus) {
R.string.favorite_added
} else {
R.string.favorite_removed
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(this@MainActivity) ?: ""
showToast(txt(message, name), Toast.LENGTH_SHORT)
}
}
if (!isTvSettings()) // dont want this clickable on tv layout
resultviewPreviewDescription.setOnClickListener { view ->
view.context?.let { ctx ->
@ -1625,7 +1682,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
// this ensures that no unnecessary space is taken
loadCache()
File(filesDir, "exoplayer").deleteRecursively() // old cache
File(cacheDir, "exoplayer").deleteOnExit() // current cache
deleteFileOnExit(File(cacheDir, "exoplayer")) // current cache
} catch (e: Exception) {
logError(e)
}

View File

@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Decode
import com.lagradost.cloudstream3.utils.*
open class Acefile : ExtractorApi() {
@ -9,31 +9,35 @@ open class Acefile : ExtractorApi() {
override val mainUrl = "https://acefile.co"
override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val sources = mutableListOf<ExtractorLink>()
app.get(url).document.select("script").map { script ->
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
val data = getAndUnpack(script.data())
val id = data.substringAfter("{\"id\":\"").substringBefore("\",")
val key = data.substringAfter("var nfck=\"").substringBefore("\";")
app.get("https://acefile.co/local/$id?key=$key").text.let {
base64Decode(
it.substringAfter("JSON.parse(atob(\"").substringBefore("\"))")
).let { res ->
sources.add(
ExtractorLink(
name,
name,
res.substringAfter("\"file\":\"").substringBefore("\","),
"$mainUrl/",
Qualities.Unknown.value,
)
)
}
}
}
}
return sources
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val id = "/(?:d|download|player|f|file)/(\\w+)".toRegex().find(url)?.groupValues?.get(1)
val script = getAndUnpack(app.get("$mainUrl/player/${id ?: return}").text)
val service = """service\s*=\s*['"]([^'"]+)""".toRegex().find(script)?.groupValues?.get(1)
val serverUrl = """['"](\S+check&id\S+?)['"]""".toRegex().find(script)?.groupValues?.get(1)
?.replace("\"+service+\"", service ?: return)
val video = app.get(serverUrl ?: return, referer = "$mainUrl/").parsedSafe<Source>()?.data
callback.invoke(
ExtractorLink(
this.name,
this.name,
video ?: return,
"",
Qualities.Unknown.value,
INFER_TYPE
)
)
}
data class Source(
val data: String? = null,
)
}

View File

@ -49,8 +49,23 @@ open class Chillx : ExtractorApi() {
val decrypt = cryptoAESHandler(master ?: return, getKey().toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
val tracks = Regex("""tracks:\s*\[(.+)]""").find(decrypt)?.groupValues?.get(1)
val subtitles = Regex("""subtitle"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
val subtitlePattern = """\[(.*?)\](https?://[^\s,]+)""".toRegex()
val matches = subtitlePattern.findAll(subtitles ?: "")
val languageUrlPairs = matches.map { matchResult ->
val (language, url) = matchResult.destructured
decodeUnicodeEscape(language) to url
}.toList()
languageUrlPairs.forEach{ (name, file) ->
subtitleCallback.invoke(
SubtitleFile(
name,
file
)
)
}
// required
val headers = mapOf(
"Accept" to "*/*",
@ -67,18 +82,15 @@ open class Chillx : ExtractorApi() {
"$mainUrl/",
headers = headers
).forEach(callback)
AppUtils.tryParseJson<List<Tracks>>("[$tracks]")
?.filter { it.kind == "captions" }?.map { track ->
subtitleCallback.invoke(
SubtitleFile(
track.label ?: "",
track.file ?: return@map null
)
)
}
}
private fun decodeUnicodeEscape(input: String): String {
val regex = Regex("u([0-9a-fA-F]{4})")
return regex.replace(input) {
it.groupValues[1].toInt(16).toChar().toString()
}
}
suspend fun getKey() = key ?: fetchKey().also { key = it }
private suspend fun fetchKey(): String {

View File

@ -18,7 +18,22 @@ open class ContentX : ExtractorApi() {
val i_source = app.get(url, referer=ext_ref).text
val i_extract = Regex("""window\.openPlayer\('([^']+)'""").find(i_source)!!.groups[1]?.value ?: throw ErrorLoadingException("i_extract is null")
val vid_source = app.get("https://contentx.me/source2.php?v=${i_extract}", referer=ext_ref).text
val sub_urls = mutableSetOf<String>()
Regex("""\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(i_source).forEach {
val (sub_url, sub_lang) = it.destructured
if (sub_url in sub_urls) { return@forEach }
sub_urls.add(sub_url)
subtitleCallback.invoke(
SubtitleFile(
lang = sub_lang.replace("\\u0131", "ı").replace("\\u0130", "İ").replace("\\u00fc", "ü").replace("\\u00e7", "ç"),
url = fixUrl(sub_url.replace("\\", ""))
)
)
}
val vid_source = app.get("${mainUrl}/source2.php?v=${i_extract}", referer=ext_ref).text
val vid_extract = Regex("""file\":\"([^\"]+)""").find(vid_source)!!.groups[1]?.value ?: throw ErrorLoadingException("vid_extract is null")
val m3u_link = vid_extract.replace("\\", "")
@ -35,7 +50,7 @@ open class ContentX : ExtractorApi() {
val i_dublaj = Regex(""",\"([^']+)\",\"Türkçe""").find(i_source)!!.groups[1]?.value
if (i_dublaj != null) {
val dublaj_source = app.get("https://contentx.me/source2.php?v=${i_dublaj}", referer=ext_ref).text
val dublaj_source = app.get("${mainUrl}/source2.php?v=${i_dublaj}", referer=ext_ref).text
val dublaj_extract = Regex("""file\":\"([^\"]+)""").find(dublaj_source)!!.groups[1]?.value ?: throw ErrorLoadingException("dublaj_extract is null")
val dublaj_link = dublaj_extract.replace("\\", "")

View File

@ -0,0 +1,39 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
open class EmturbovidExtractor : ExtractorApi() {
override var name = "Emturbovid"
override var mainUrl = "https://emturbovid.com"
override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val response = app.get(
url, referer = referer ?: "$mainUrl/"
)
val playerScript =
response.document.selectXpath("//script[contains(text(),'var urlPlay')]")
.html()
val sources = mutableListOf<ExtractorLink>()
if (playerScript.isNotBlank()) {
val m3u8Url =
playerScript.substringAfter("var urlPlay = '").substringBefore("'")
sources.add(
ExtractorLink(
source = name,
name = name,
url = m3u8Url,
referer = "$mainUrl/",
quality = Qualities.Unknown.value,
isM3u8 = true
)
)
}
return sources
}
}

View File

@ -22,9 +22,9 @@ open class Gofile : ExtractorApi() {
val id = Regex("/(?:\\?c=|d/)([\\da-zA-Z-]+)").find(url)?.groupValues?.get(1)
val token = app.get("$mainApi/createAccount").parsedSafe<Account>()?.data?.get("token")
val websiteToken = app.get("$mainUrl/dist/js/alljs.js").text.let {
Regex("websiteToken\\s*=\\s*\"([^\"]+)").find(it)?.groupValues?.get(1)
Regex("fetchData.wt\\s*=\\s*\"([^\"]+)").find(it)?.groupValues?.get(1)
}
app.get("$mainApi/getContent?contentId=$id&token=$token&websiteToken=$websiteToken")
app.get("$mainApi/getContent?contentId=$id&token=$token&wt=$websiteToken")
.parsedSafe<Source>()?.data?.contents?.forEach {
callback.invoke(
ExtractorLink(

View File

@ -5,4 +5,19 @@ package com.lagradost.cloudstream3.extractors
class Hotlinger : ContentX() {
override var name = "Hotlinger"
override var mainUrl = "https://hotlinger.com"
}
class FourCX : ContentX() {
override var name = "FourCX"
override var mainUrl = "https://four.contentx.me"
}
class PlayRu : ContentX() {
override var name = "PlayRu"
override var mainUrl = "https://playru.net"
}
class FourPlayRu : ContentX() {
override var name = "FourPlayRu"
override var mainUrl = "https://four.playru.net"
}

View File

@ -0,0 +1,43 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.Qualities
open class Mediafire : ExtractorApi() {
override val name = "Mediafire"
override val mainUrl = "https://www.mediafire.com"
override val requiresReferer = true
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val res = app.get(url, referer = referer).document
val title = res.select("div.dl-btn-label").text()
val video = res.selectFirst("a#downloadButton")?.attr("href")
callback.invoke(
ExtractorLink(
this.name,
this.name,
video ?: return,
"",
getQuality(title),
INFER_TYPE
)
)
}
private fun getQuality(str: String?): Int {
return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull()
?: Qualities.Unknown.value
}
}

View File

@ -2,7 +2,6 @@
package com.lagradost.cloudstream3.extractors
import android.util.Log
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
@ -12,19 +11,14 @@ open class PixelDrain : ExtractorApi() {
override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val ext_ref = referer ?: ""
val pixel_id = Regex("""([^\/]+)(?=\?download)""").find(url)?.groupValues?.get(1)
val downloadLink = "${mainUrl}/api/file/${pixel_id}?download"
Log.d("Kekik_${this.name}", "downloadLink » ${downloadLink}")
val mId = Regex("/([ul]/[\\da-zA-Z\\-]+)(?:\\?download)?").find(url)?.groupValues?.get(1)?.split("/")
callback.invoke(
ExtractorLink(
source = "pixeldrain - ${pixel_id}",
name = "pixeldrain - ${pixel_id}",
url = downloadLink,
referer = "${mainUrl}/u/${pixel_id}?download",
quality = Qualities.Unknown.value,
type = INFER_TYPE
this.name,
this.name,
"$mainUrl/api/file/${mId?.last() ?: return}?download",
url,
Qualities.Unknown.value,
)
)
}

View File

@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64DecodeArray
import com.lagradost.cloudstream3.base64Encode
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorApi
@ -16,13 +17,52 @@ import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
// Code found in https://github.com/Claudemirovsky/keys
// special credits to @Claudemirovsky for providing key
class Megacloud : Rabbitstream() {
override val name = "Megacloud"
override val mainUrl = "https://megacloud.tv"
override val embed = "embed-2/ajax/e-1"
override val key = "https://raw.githubusercontent.com/Claudemirovsky/keys/e1/key"
private val scriptUrl = "$mainUrl/js/player/a/prod/e1-player.min.js"
override suspend fun extractRealKey(sources: String): Pair<String, String> {
val rawKeys = getKeys()
val sourcesArray = sources.toCharArray()
var extractedKey = ""
var currentIndex = 0
for (index in rawKeys) {
val start = index[0] + currentIndex
val end = start + index[1]
for (i in start until end) {
extractedKey += sourcesArray[i].toString()
sourcesArray[i] = ' '
}
currentIndex += index[1]
}
return extractedKey to sourcesArray.joinToString("").replace(" ", "")
}
private suspend fun getKeys(): List<List<Int>> {
val script = app.get(scriptUrl).text
fun matchingKey(value: String): String {
return Regex(",$value=((?:0x)?([0-9a-fA-F]+))").find(script)?.groupValues?.get(1)
?.removePrefix("0x") ?: throw ErrorLoadingException("Failed to match the key")
}
val regex = Regex("case\\s*0x[0-9a-f]+:(?![^;]*=partKey)\\s*\\w+\\s*=\\s*(\\w+)\\s*,\\s*\\w+\\s*=\\s*(\\w+);")
val indexPairs = regex.findAll(script).toList().map { match ->
val matchKey1 = matchingKey(match.groupValues[1])
val matchKey2 = matchingKey(match.groupValues[2])
try {
listOf(matchKey1.toInt(16), matchKey2.toInt(16))
} catch (e: NumberFormatException) {
emptyList()
}
}.filter { it.isNotEmpty() }
return indexPairs
}
}
class Dokicloud : Rabbitstream() {
@ -30,12 +70,14 @@ class Dokicloud : Rabbitstream() {
override val mainUrl = "https://dokicloud.one"
}
// Code found in https://github.com/eatmynerds/key
// special credits to @eatmynerds for providing key
open class Rabbitstream : ExtractorApi() {
override val name = "Rabbitstream"
override val mainUrl = "https://rabbitstream.net"
override val requiresReferer = false
open val embed = "ajax/embed-4"
open val key = "https://raw.githubusercontent.com/Claudemirovsky/keys/e4/key"
open val key = "https://raw.githubusercontent.com/eatmynerds/key/e4/key.txt"
override suspend fun getUrl(
url: String,
@ -56,7 +98,7 @@ open class Rabbitstream : ExtractorApi() {
val decryptedSources = if (sources == null || encryptedMap.encrypted == false) {
response.parsedSafe()
} else {
val (key, encData) = extractRealKey(sources, getRawKey())
val (key, encData) = extractRealKey(sources)
val decrypted = decryptMapped<List<Sources>>(encData, key)
SourcesResponses(
sources = decrypted,
@ -75,8 +117,8 @@ open class Rabbitstream : ExtractorApi() {
decryptedSources?.tracks?.map { track ->
subtitleCallback.invoke(
SubtitleFile(
track?.label ?: "",
track?.file ?: return@map
track?.label ?: return@map,
track.file ?: return@map
)
)
}
@ -84,25 +126,10 @@ open class Rabbitstream : ExtractorApi() {
}
private suspend fun getRawKey(): String = app.get(key).text
private fun extractRealKey(sources: String, stops: String): Pair<String, String> {
val decryptKey = parseJson<List<List<Int>>>(stops)
val sourcesArray = sources.toCharArray()
var extractedKey = ""
var currentIndex = 0
for (index in decryptKey) {
val start = index[0] + currentIndex
val end = start + index[1]
for (i in start until end) {
extractedKey += sourcesArray[i].toString()
sourcesArray[i] = ' '
}
currentIndex += index[1]
}
return extractedKey to sourcesArray.joinToString("")
open suspend fun extractRealKey(sources: String): Pair<String, String> {
val rawKeys = parseJson<List<Int>>(app.get(key).text)
val extractedKey = base64Encode(rawKeys.map { it.toByte() }.toByteArray())
return extractedKey to sources
}
private inline fun <reified T> decryptMapped(input: String, key: String): T? {

View File

@ -9,6 +9,10 @@ class StreamTapeNet : StreamTape() {
override var mainUrl = "https://streamtape.net"
}
class StreamTapeXyz : StreamTape() {
override var mainUrl = "https://streamtape.xyz"
}
class ShaveTape : StreamTape(){
override var mainUrl = "https://shavetape.cash"
}

View File

@ -0,0 +1,34 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.network.WebViewResolver
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
open class StreamWishExtractor : ExtractorApi() {
override var name = "StreamWish"
override var mainUrl = "https://streamwish.to"
override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val response = app.get(
url, referer = referer ?: "$mainUrl/", interceptor = WebViewResolver(
Regex("""master\.m3u8""")
)
)
val sources = mutableListOf<ExtractorLink>()
if (response.url.contains("m3u8"))
sources.add(
ExtractorLink(
source = name,
name = name,
url = response.url,
referer = referer ?: "$mainUrl/",
quality = Qualities.Unknown.value,
isM3u8 = true
)
)
return sources
}
}

View File

@ -0,0 +1,34 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.network.WebViewResolver
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
open class VidhideExtractor : ExtractorApi() {
override var name = "VidHide"
override var mainUrl = "https://vidhide.com"
override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val response = app.get(
url, referer = referer ?: "$mainUrl/", interceptor = WebViewResolver(
Regex("""master\.m3u8""")
)
)
val sources = mutableListOf<ExtractorLink>()
if (response.url.contains("m3u8"))
sources.add(
ExtractorLink(
source = name,
name = name,
url = response.url,
referer = referer ?: "$mainUrl/",
quality = Qualities.Unknown.value,
isM3u8 = true
)
)
return sources
}
}

View File

@ -10,13 +10,24 @@ import com.lagradost.cloudstream3.utils.M3u8Helper
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
// Code found in https://github.com/Claudemirovsky/worstsource-keys
// special credits to @Claudemirovsky for providing key
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys
// special credits to @KillerDogeEmpire for providing key
class MyCloud : Vidplay() {
override val name = "MyCloud"
override val mainUrl = "https://mcloud.bz"
}
class VidplayOnline : Vidplay() {
override val mainUrl = "https://vidplay.online"
}
open class Vidplay : ExtractorApi() {
override val name = "Vidplay"
override val mainUrl = "https://vidplay.site"
override val requiresReferer = true
open val key = "https://raw.githubusercontent.com/Claudemirovsky/worstsource-keys/keys/keys.json"
open val key =
"https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json"
override suspend fun getUrl(
url: String,
@ -32,9 +43,9 @@ open class Vidplay : ExtractorApi() {
"Accept" to "application/json, text/javascript, */*; q=0.01",
"X-Requested-With" to "XMLHttpRequest",
), referer = url
).parsedSafe<Response>()?.result?.sources
).parsedSafe<Response>()?.result
res?.map {
res?.sources?.map {
M3u8Helper.generateM3u8(
this.name,
it.file ?: return@map,
@ -42,6 +53,12 @@ open class Vidplay : ExtractorApi() {
).forEach(callback)
}
res?.tracks?.filter { it.kind == "captions" }?.map {
subtitleCallback.invoke(
SubtitleFile(it.label ?: return@map, it.file ?: return@map)
)
}
}
private suspend fun getKeys(): List<String> {
@ -77,12 +94,19 @@ open class Vidplay : ExtractorApi() {
return base64Encode(input).replace("/", "_")
}
data class Tracks(
@JsonProperty("file") val file: String? = null,
@JsonProperty("label") val label: String? = null,
@JsonProperty("kind") val kind: String? = null,
)
data class Sources(
@JsonProperty("file") val file: String? = null,
)
data class Result(
@JsonProperty("sources") val sources: ArrayList<Sources>? = arrayListOf(),
@JsonProperty("tracks") val tracks: ArrayList<Tracks>? = arrayListOf(),
)
data class Response(

View File

@ -70,19 +70,18 @@ open class YoutubeExtractor : ExtractorApi() {
}
}
ytVideos[url]?.mapNotNull {
if (it.isVideoOnly || it.height <= 0) return@mapNotNull null
if (it.isVideoOnly() || it.height <= 0) return@mapNotNull null
ExtractorLink(
this.name,
this.name,
it.url ?: return@mapNotNull null,
it.content ?: return@mapNotNull null,
"",
it.height
)
}?.forEach(callback)
ytVideosSubtitles[url]?.mapNotNull {
SubtitleFile(it.languageTag ?: return@mapNotNull null, it.url ?: return@mapNotNull null)
SubtitleFile(it.languageTag ?: return@mapNotNull null, it.content ?: return@mapNotNull null)
}?.forEach(subtitleCallback)
}
}

View File

@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.network
import android.annotation.SuppressLint
import android.net.http.SslError
import android.os.Handler
import android.os.Looper
import android.webkit.*
import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.AcraApplication.Companion.context
@ -27,16 +29,39 @@ import java.net.URI
* @param additionalUrls this will make resolveUsingWebView also return all other requests matching the list of Regex.
* @param userAgent if null then will use the default user agent
* @param useOkhttp will try to use the okhttp client as much as possible, but this might cause some requests to fail. Disable for cloudflare.
* @param script pass custom js to execute
* @param scriptCallback will be called with the result from custom js
* @param timeout close webview after timeout
* */
class WebViewResolver(
val interceptUrl: Regex,
val additionalUrls: List<Regex> = emptyList(),
val userAgent: String? = USER_AGENT,
val useOkhttp: Boolean = true
val useOkhttp: Boolean = true,
val script: String? = null,
val scriptCallback: ((String) -> Unit)? = null,
val timeout: Long = DEFAULT_TIMEOUT
) :
Interceptor {
constructor(
interceptUrl: Regex,
additionalUrls: List<Regex> = emptyList(),
userAgent: String? = USER_AGENT,
useOkhttp: Boolean = true,
script: String? = null,
scriptCallback: ((String) -> Unit)? = null,
) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, script, scriptCallback, DEFAULT_TIMEOUT)
constructor(
interceptUrl: Regex,
additionalUrls: List<Regex> = emptyList(),
userAgent: String? = USER_AGENT,
useOkhttp: Boolean = true
) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, null, null, DEFAULT_TIMEOUT)
companion object {
private const val DEFAULT_TIMEOUT = 60_000L
var webViewUserAgent: String? = null
@JvmName("getWebViewUserAgent1")
@ -136,6 +161,14 @@ class WebViewResolver(
val webViewUrl = request.url.toString()
println("Loading WebView URL: $webViewUrl")
if (script != null) {
val handler = Handler(Looper.getMainLooper())
handler.post {
view.evaluateJavascript("$script")
{ scriptCallback?.invoke(it) }
}
}
if (interceptUrl.containsMatchIn(webViewUrl)) {
fixedRequest = request.toRequest()?.also {
requestCallBack(it)
@ -241,7 +274,7 @@ class WebViewResolver(
var loop = 0
// Timeouts after this amount, 60s
val totalTime = 60000L
val totalTime = timeout
val delayTime = 100L

View File

@ -1,11 +1,23 @@
package com.lagradost.cloudstream3.subtitles
import androidx.annotation.WorkerThread
import androidx.core.net.toUri
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleEntity
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleSearch
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.ui.player.SubtitleOrigin
import okio.BufferedSource
import okio.buffer
import okio.sink
import okio.source
import java.io.File
import java.util.zip.ZipInputStream
interface AbstractSubProvider {
val idPrefix: String
@WorkerThread
suspend fun search(query: SubtitleSearch): List<SubtitleEntity>? {
throw NotImplementedError()
@ -15,6 +27,98 @@ interface AbstractSubProvider {
suspend fun load(data: SubtitleEntity): String? {
throw NotImplementedError()
}
@WorkerThread
suspend fun SubtitleResource.getResources(data: SubtitleEntity) {
this.addUrl(load(data))
}
@WorkerThread
suspend fun getResource(data: SubtitleEntity): SubtitleResource {
return SubtitleResource().apply {
this.getResources(data)
}
}
}
/**
* A builder for subtitle files.
* @see addUrl
* @see addFile
*/
class SubtitleResource {
fun downloadFile(source: BufferedSource): File {
val file = File.createTempFile("temp-subtitle", ".tmp").apply {
deleteFileOnExit(this)
}
val sink = file.sink().buffer()
sink.writeAll(source)
sink.close()
source.close()
return file
}
fun unzip(file: File): List<Pair<String, File>> {
val entries = mutableListOf<Pair<String, File>>()
ZipInputStream(file.inputStream()).use { zipInputStream ->
var zipEntry = zipInputStream.nextEntry
while (zipEntry != null) {
val tempFile = File.createTempFile("unzipped-subtitle", ".tmp").apply {
deleteFileOnExit(this)
}
entries.add(zipEntry.name to tempFile)
tempFile.sink().buffer().use { buffer ->
buffer.writeAll(zipInputStream.source())
}
zipEntry = zipInputStream.nextEntry
}
}
return entries
}
data class SingleSubtitleResource(
val name: String?,
val url: String,
val origin: SubtitleOrigin
)
private var resources: MutableList<SingleSubtitleResource> = mutableListOf()
fun getSubtitles(): List<SingleSubtitleResource> {
return resources.toList()
}
fun addUrl(url: String?, name: String? = null) {
if (url == null) return
this.resources.add(
SingleSubtitleResource(name, url, SubtitleOrigin.URL)
)
}
fun addFile(file: File, name: String? = null) {
this.resources.add(
SingleSubtitleResource(name, file.toUri().toString(), SubtitleOrigin.DOWNLOADED_FILE)
)
deleteFileOnExit(file)
}
suspend fun addZipUrl(
url: String,
nameGenerator: (String, File) -> String? = { _, _ -> null }
) {
val source = app.get(url).okhttpResponse.body.source()
val zip = downloadFile(source)
val realFiles = unzip(zip)
zip.deleteRecursively()
realFiles.forEach { (name, subtitleFile) ->
addFile(subtitleFile, nameGenerator(name, subtitleFile))
}
}
}
interface AbstractSubApi : AbstractSubProvider, AuthAPI

View File

@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.syncproviders
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.syncproviders.providers.SubScene
import com.lagradost.cloudstream3.syncproviders.providers.*
import java.util.concurrent.TimeUnit
@ -14,6 +15,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
val simklApi = SimklApi(0)
val indexSubtitlesApi = IndexSubtitleApi()
val addic7ed = Addic7ed()
val subScene = SubScene()
val localListApi = LocalList()
// used to login via app intent
@ -41,7 +43,8 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
get() = listOf(
openSubtitlesApi,
indexSubtitlesApi, // they got anti scraping measures in place :(
addic7ed
addic7ed,
subScene
)
const val appString = "cloudstreamapp"

View File

@ -23,6 +23,47 @@ class IndexSubtitleApi : AbstractSubApi {
companion object {
const val host = "https://indexsubtitle.com"
const val TAG = "INDEXSUBS"
fun getOrdinal(num: Int?): String? {
return when (num) {
1 -> "First"
2 -> "Second"
3 -> "Third"
4 -> "Fourth"
5 -> "Fifth"
6 -> "Sixth"
7 -> "Seventh"
8 -> "Eighth"
9 -> "Ninth"
10 -> "Tenth"
11 -> "Eleventh"
12 -> "Twelfth"
13 -> "Thirteenth"
14 -> "Fourteenth"
15 -> "Fifteenth"
16 -> "Sixteenth"
17 -> "Seventeenth"
18 -> "Eighteenth"
19 -> "Nineteenth"
20 -> "Twentieth"
21 -> "Twenty-First"
22 -> "Twenty-Second"
23 -> "Twenty-Third"
24 -> "Twenty-Fourth"
25 -> "Twenty-Fifth"
26 -> "Twenty-Sixth"
27 -> "Twenty-Seventh"
28 -> "Twenty-Eighth"
29 -> "Twenty-Ninth"
30 -> "Thirtieth"
31 -> "Thirty-First"
32 -> "Thirty-Second"
33 -> "Thirty-Third"
34 -> "Thirty-Fourth"
35 -> "Thirty-Fifth"
else -> null
}
}
}
private fun fixUrl(url: String): String {
@ -44,47 +85,6 @@ class IndexSubtitleApi : AbstractSubApi {
}
}
private fun getOrdinal(num: Int?): String? {
return when (num) {
1 -> "First"
2 -> "Second"
3 -> "Third"
4 -> "Fourth"
5 -> "Fifth"
6 -> "Sixth"
7 -> "Seventh"
8 -> "Eighth"
9 -> "Ninth"
10 -> "Tenth"
11 -> "Eleventh"
12 -> "Twelfth"
13 -> "Thirteenth"
14 -> "Fourteenth"
15 -> "Fifteenth"
16 -> "Sixteenth"
17 -> "Seventeenth"
18 -> "Eighteenth"
19 -> "Nineteenth"
20 -> "Twentieth"
21 -> "Twenty-First"
22 -> "Twenty-Second"
23 -> "Twenty-Third"
24 -> "Twenty-Fourth"
25 -> "Twenty-Fifth"
26 -> "Twenty-Sixth"
27 -> "Twenty-Seventh"
28 -> "Twenty-Eighth"
29 -> "Twenty-Ninth"
30 -> "Thirtieth"
31 -> "Thirty-First"
32 -> "Thirty-Second"
33 -> "Thirty-Third"
34 -> "Thirty-Fourth"
35 -> "Thirty-Fifth"
else -> null
}
}
private fun isRightEps(text: String, seasonNum: Int?, epNum: Int?): Boolean {
val FILTER_EPS_REGEX =
Regex("(?i)((Chapter\\s?0?${epNum})|((Season)?\\s?0?${seasonNum}?\\s?(Episode)\\s?0?${epNum}[^0-9]))|(?i)((S?0?${seasonNum}?E0?${epNum}[^0-9])|(0?${seasonNum}[a-z]0?${epNum}[^0-9]))")

View File

@ -0,0 +1,118 @@
package com.lagradost.cloudstream3.syncproviders.providers
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.debugPrint
import com.lagradost.cloudstream3.subtitles.AbstractSubProvider
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.subtitles.SubtitleResource
import com.lagradost.cloudstream3.syncproviders.providers.IndexSubtitleApi.Companion.getOrdinal
import com.lagradost.cloudstream3.utils.SubtitleHelper
class SubScene : AbstractSubProvider {
val mainUrl = "https://subscene.com"
val name = "Subscene"
override val idPrefix = "subscene"
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
val seasonName =
query.seasonNumber?.let { number ->
// Need to translate "7" to "Seventh Season"
getOrdinal(number)?.let { words -> " - $words Season" }
} ?: ""
val fullQuery = query.query + seasonName
val doc = app.post(
"$mainUrl/subtitles/searchbytitle",
data = mapOf("query" to fullQuery, "l" to "")
).document
return doc.select("div.title a").map { element ->
val href = "$mainUrl${element.attr("href")}"
val title = element.text()
AbstractSubtitleEntities.SubtitleEntity(
idPrefix = idPrefix,
name = title,
source = name,
data = href,
lang = query.lang ?: "en",
epNumber = query.epNumber
)
}.distinctBy { it.data }
}
override suspend fun SubtitleResource.getResources(data: AbstractSubtitleEntities.SubtitleEntity) {
val resultDoc = app.get(data.data).document
val queryLanguage = SubtitleHelper.fromTwoLettersToLanguage(data.lang) ?: "English"
val results = resultDoc.select("table tbody tr").mapNotNull { element ->
val anchor = element.select("a")
val href = anchor.attr("href") ?: return@mapNotNull null
val fixedHref = "$mainUrl${href}"
val spans = anchor.select("span")
val language = spans.firstOrNull()?.text()
val title = spans.getOrNull(1)?.text()
val isPositive = anchor.select("span.positive-icon").isNotEmpty()
TableElement(title, language, fixedHref, isPositive)
}.sortedBy {
it.getScore(queryLanguage, data.epNumber)
}
debugPrint { "$name found subtitles: ${results.takeLast(3)}" }
// Last = highest score
val selectedResult = results.lastOrNull() ?: return
val subtitleDocument = app.get(selectedResult.href).document
val subtitleDownloadUrl =
"$mainUrl${subtitleDocument.select("div.download a").attr("href")}"
this.addZipUrl(subtitleDownloadUrl) { name, _ ->
name
}
}
/**
* Class to manage the various different subtitle results and rank them.
*/
data class TableElement(
val title: String?,
val language: String?,
val href: String,
val isPositive: Boolean
) {
private fun matchesLanguage(other: String): Boolean {
return language != null && (language.contains(other, ignoreCase = true) ||
other.contains(language, ignoreCase = true))
}
/**
* Scores in this order:
* Preferred Language > Episode number > Positive rating > English Language
*/
fun getScore(queryLanguage: String, episodeNum: Int?): Int {
var score = 0
if (this.matchesLanguage(queryLanguage)) {
score += 8
}
// Matches Episode 7 using "E07" with any number of leading zeroes
if (episodeNum != null && title != null && title.contains(
Regex(
"""E0*${episodeNum}""",
RegexOption.IGNORE_CASE
)
)
) {
score += 4
}
if (isPositive) {
score += 2
}
if (this.matchesLanguage("English")) {
score += 1
}
return score
}
}
}

View File

@ -8,7 +8,7 @@ import android.widget.*
import androidx.appcompat.app.AlertDialog
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaSeekOptions
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
@ -98,7 +98,7 @@ data class MetadataHolder(
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) :
UIController() {
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
private val mapper: JsonMapper = JsonMapper.builder().addModule(kotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
init {

View File

@ -216,6 +216,9 @@ class HomeParentItemAdapterPreview(
viewModel.click(callback)
return@HomeChildItemAdapter
}
(callback.view.context?.getActivity() as? MainActivity)?.loadPopup(callback.card, load = false)
/*
callback.view.context?.getActivity()?.showOptionSelectStringRes(
callback.view,
callback.card.posterUrl,
@ -261,6 +264,7 @@ class HomeParentItemAdapterPreview(
}
}
}
*/
}
@ -313,7 +317,7 @@ class HomeParentItemAdapterPreview(
homePreviewText.text = item.name
populateChips(
homePreviewTags,
item.tags ?: emptyList(),
item.tags?.take(6) ?: emptyList(),
R.style.ChipFilledSemiTransparent
)

View File

@ -81,6 +81,7 @@ class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
binding.homeScrollPreviewTags.apply {
text = card.tags?.joinToString("") ?: ""
isGone = card.tags.isNullOrEmpty()
maxLines = 2
}
binding.homeScrollPreviewTitle.text = card.name
}

View File

@ -20,11 +20,12 @@ import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.widget.SearchView
import androidx.core.view.allViews
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.lagradost.cloudstream3.APIHolder
@ -35,6 +36,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentLibraryBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.debugAssert
@ -80,6 +82,8 @@ data class ProviderLibraryData(
class LibraryFragment : Fragment() {
companion object {
val listLibraryItems = mutableListOf<SyncAPI.LibraryItem>()
fun newInstance() = LibraryFragment()
/**
@ -91,6 +95,7 @@ class LibraryFragment : Fragment() {
private val libraryViewModel: LibraryViewModel by activityViewModels()
var binding: FragmentLibraryBinding? = null
private var toggleRandomButton = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@ -196,6 +201,25 @@ class LibraryFragment : Fragment() {
}
}
//Load value for toggling Random button. Hide at startup
context?.let {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
toggleRandomButton =
settingsManager.getBoolean(
getString(R.string.random_button_key),
false
) && !SettingsFragment.isTvSettings()
binding?.libraryRandom?.visibility = View.GONE
}
binding?.libraryRandom?.setOnClickListener {
if (listLibraryItems.isNotEmpty()) {
val listLibraryItem = listLibraryItems.random()
libraryViewModel.currentSyncApi?.syncIdName?.let {
loadLibraryItem(it, listLibraryItem.syncId,listLibraryItem)
}
}
}
/**
* Shows a plugin selection dialogue and saves the response
@ -277,8 +301,10 @@ class LibraryFragment : Fragment() {
{ isScrollingDown: Boolean ->
if (isScrollingDown) {
binding?.sortFab?.shrink()
binding?.libraryRandom?.shrink()
} else {
binding?.sortFab?.extend()
binding?.libraryRandom?.extend()
}
}) callback@{ searchClickCallback ->
// To prevent future accidents
@ -303,52 +329,7 @@ class LibraryFragment : Fragment() {
}
SEARCH_ACTION_LOAD -> {
// This basically first selects the individual opener and if that is default then
// selects the whole list opener
val savedListSelection =
getKey<LibraryOpener>("$currentAccount/$LIBRARY_FOLDER", syncName.name)
val savedSelection = getKey<LibraryOpener>(
"$currentAccount/$LIBRARY_FOLDER",
syncId
).takeIf {
it?.openType != LibraryOpenerType.Default
} ?: savedListSelection
when (savedSelection?.openType) {
null, LibraryOpenerType.Default -> {
// Prevents opening MAL/AniList as a provider
if (APIHolder.getApiFromNameNull(searchClickCallback.card.apiName) != null) {
activity?.loadSearchResult(
searchClickCallback.card
)
} else {
// Search when no provider can open
QuickSearchFragment.pushSearch(
activity,
searchClickCallback.card.name
)
}
}
LibraryOpenerType.None -> {}
LibraryOpenerType.Provider ->
savedSelection.providerData?.apiName?.let { apiName ->
activity?.loadResult(
searchClickCallback.card.url,
apiName,
)
}
LibraryOpenerType.Browser ->
openBrowser(searchClickCallback.card.url)
LibraryOpenerType.Search -> {
QuickSearchFragment.pushSearch(
activity,
searchClickCallback.card.name
)
}
}
loadLibraryItem(syncName, syncId, searchClickCallback.card)
}
}
}
@ -414,6 +395,16 @@ class LibraryFragment : Fragment() {
binding?.viewpager?.setCurrentItem(page, false)
}
observe(libraryViewModel.currentPage){
if (toggleRandomButton) {
listLibraryItems.clear()
listLibraryItems.addAll(pages[it].items)
libraryRandom.isVisible = listLibraryItems.isNotEmpty()
} else {
libraryRandom.isGone = true
}
}
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
// Without this there would be a flashing effect:
// loading -> show old viewpager -> black screen -> show new viewpager
@ -512,6 +503,62 @@ class LibraryFragment : Fragment() {
}
})*/
}
private fun loadLibraryItem(
syncName: SyncIdName,
syncId: String,
card: SearchResponse
) {
// This basically first selects the individual opener and if that is default then
// selects the whole list opener
val savedListSelection =
getKey<LibraryOpener>("$currentAccount/$LIBRARY_FOLDER", syncName.name)
val savedSelection = getKey<LibraryOpener>(
"$currentAccount/$LIBRARY_FOLDER",
syncId
).takeIf {
it?.openType != LibraryOpenerType.Default
} ?: savedListSelection
when (savedSelection?.openType) {
null, LibraryOpenerType.Default -> {
// Prevents opening MAL/AniList as a provider
if (APIHolder.getApiFromNameNull(card.apiName) != null) {
activity?.loadSearchResult(
card
)
} else {
// Search when no provider can open
QuickSearchFragment.pushSearch(
activity,
card.name
)
}
}
LibraryOpenerType.None -> {}
LibraryOpenerType.Provider ->
savedSelection.providerData?.apiName?.let { apiName ->
activity?.loadResult(
card.url,
apiName,
)
}
LibraryOpenerType.Browser ->
openBrowser(card.url)
LibraryOpenerType.Search -> {
QuickSearchFragment.pushSearch(
activity,
card.name
)
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
(binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind()
super.onConfigurationChanged(newConfig)

View File

@ -102,6 +102,8 @@ abstract class AbstractPlayerFragment(
throw NotImplementedError()
}
open fun playerStatusChanged(){}
open fun playerDimensionsLoaded(width: Int, height: Int) {
throw NotImplementedError()
}
@ -431,6 +433,7 @@ abstract class AbstractPlayerFragment(
is StatusEvent -> {
updateIsPlaying(wasPlaying = event.wasPlaying, isPlaying = event.isPlaying)
playerStatusChanged()
}
is PositionEvent -> {

View File

@ -50,6 +50,7 @@ import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.debugAssert
@ -657,7 +658,7 @@ class CS3IPlayer : IPlayer {
SimpleCache(
File(
context.cacheDir, "exoplayer"
).also { it.deleteOnExit() }, // Ensures always fresh file
).also { deleteFileOnExit(it) }, // Ensures always fresh file
LeastRecentlyUsedCacheEvictor(cacheSize),
databaseProvider
)

View File

@ -7,14 +7,13 @@ import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.text.Editable
import android.util.DisplayMetrics
import android.text.format.DateUtils
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
@ -31,11 +30,10 @@ import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.CommonActivity.screenHeight
@ -48,6 +46,7 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvid
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -59,10 +58,10 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.UserPreferenceDelegate
import com.lagradost.cloudstream3.utils.Vector2
import kotlin.math.*
const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking
const val MINIMUM_VERTICAL_SWIPE = 2.0f // in percentage
const val MINIMUM_HORIZONTAL_SWIPE = 2.0f // in percentage
@ -79,10 +78,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
protected open var lockRotation = true
protected open var isFullScreenPlayer = true
protected open var isTv = false
protected var playerBinding: PlayerCustomLayoutBinding? = null
private var durationMode : Boolean by UserPreferenceDelegate("duration_mode", false)
// state of player UI
protected var isShowing = false
protected var isLocked = false
@ -379,7 +377,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
}
protected fun exitFullscreen() {
activity?.showSystemUI()
//if (lockRotation)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
@ -391,6 +388,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
}
activity?.window?.attributes = lp
activity?.showSystemUI()
}
override fun onResume() {
@ -1332,15 +1330,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} else false
}
//player_episodes_button?.setOnClickListener {
// player_episodes_button?.isGone = true
// player_episode_list?.isVisible = true
//}
//
//player_episode_list?.adapter = PlayerEpisodeAdapter { click ->
//
//}
try {
context?.let { ctx ->
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
@ -1425,12 +1414,21 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} catch (e: Exception) {
logError(e)
}
playerBinding?.apply {
playerPausePlay.setOnClickListener {
autoHide()
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
}
exoDuration.setOnClickListener {
setRemainingTimeCounter(true)
}
timeLeft.setOnClickListener {
setRemainingTimeCounter(false)
}
skipChapterButton.setOnClickListener {
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
}
@ -1515,32 +1513,14 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
return@setOnTouchListener false
}
}
// cs3 is peak media center
setRemainingTimeCounter(durationMode || SettingsFragment.isTrueTvSettings())
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
updateRemainingTime()
}
// init UI
try {
uiReset()
// init chromecast UI
// removed due to having no use and bugging
//activity?.let {
// if (it.isCastApiAvailable()) {
// try {
// CastButtonFactory.setUpMediaRouteButton(it, player_media_route_button)
// val castContext = CastContext.getSharedInstance(it.applicationContext)
//
// player_media_route_button?.isGone =
// castContext.castState == CastState.NO_DEVICES_AVAILABLE
// castContext.addCastStateListener { state ->
// player_media_route_button?.isGone =
// state == CastState.NO_DEVICES_AVAILABLE
// }
// } catch (e: Exception) {
// logError(e)
// }
// } else {
// // if cast is not possible hide UI
// player_media_route_button?.isGone = true
// }
//}
} catch (e: Exception) {
logError(e)
}
@ -1558,6 +1538,24 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
updateOrientation()
}
private fun updateRemainingTime() {
val duration = player.getDuration()
val position = player.getPosition()
if (duration != null && duration > 1 && position != null) {
val remainingTimeSeconds = (duration - position + 500) / 1000
val formattedTime = "-${DateUtils.formatElapsedTime(remainingTimeSeconds)}"
playerBinding?.timeLeft?.text = formattedTime
}
}
private fun setRemainingTimeCounter(showRemaining: Boolean) {
durationMode = showRemaining
playerBinding?.exoDuration?.isInvisible= showRemaining
playerBinding?.timeLeft?.isVisible = showRemaining
}
private fun dynamicOrientation(): Int {
return if (autoPlayerRotateEnabled) {
if (isVerticalOrientation) {
@ -1569,4 +1567,4 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE // default orientation
}
}
}
}

View File

@ -30,6 +30,7 @@ import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
import com.lagradost.cloudstream3.databinding.PlayerSelectSourceAndSubsBinding
import com.lagradost.cloudstream3.databinding.PlayerSelectTracksBinding
import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitleProviders
import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage
@ -69,7 +70,10 @@ class GeneratorPlayer : FullScreenPlayer() {
}
val subsProviders
get() = subtitleProviders.filter { !it.requiresLogin || it.loginInfo() != null }
get() = subtitleProviders.filter { provider ->
(provider as? AbstractSubApi)?.let { !it.requiresLogin || it.loginInfo() != null }
?: true
}
val subsProvidersIsActive
get() = subsProviders.isNotEmpty()
}
@ -146,6 +150,12 @@ class GeneratorPlayer : FullScreenPlayer() {
}
}
override fun playerStatusChanged() {
if (player.getIsPlaying()) {
viewModel.forceClearCache = false
}
}
private fun noSubtitles(): Boolean {
return setSubtitles(null)
}
@ -467,17 +477,21 @@ class GeneratorPlayer : FullScreenPlayer() {
currentSubtitle?.let { currentSubtitle ->
providers.firstOrNull { it.idPrefix == currentSubtitle.idPrefix }?.let { api ->
ioSafe {
val url = api.load(currentSubtitle) ?: return@ioSafe
val subtitle = SubtitleData(
name = getName(currentSubtitle, true),
url = url,
origin = SubtitleOrigin.URL,
mimeType = url.toSubtitleMimeType(),
headers = currentSubtitle.headers,
currentSubtitle.lang
)
runOnMainThread {
addAndSelectSubtitles(subtitle)
val subtitles =
api.getResource(currentSubtitle).getSubtitles().map { resource ->
SubtitleData(
name = resource.name ?: getName(currentSubtitle, true),
url = resource.url,
origin = resource.origin,
mimeType = resource.url.toSubtitleMimeType(),
headers = currentSubtitle.headers,
currentSubtitle.lang
)
}
if (subtitles.isNotEmpty()) {
runOnMainThread {
addAndSelectSubtitles(*subtitles.toTypedArray())
}
}
}
}
@ -515,7 +529,11 @@ class GeneratorPlayer : FullScreenPlayer() {
}
}
private fun addAndSelectSubtitles(subtitleData: SubtitleData) {
private fun addAndSelectSubtitles(
vararg subtitleData: SubtitleData
) {
if (subtitleData.isEmpty()) return
val selectedSubtitle = subtitleData.first()
val ctx = context ?: return
val subs = currentSubs + subtitleData
@ -527,13 +545,13 @@ class GeneratorPlayer : FullScreenPlayer() {
player.saveData()
player.reloadPlayer(ctx)
setSubtitles(subtitleData)
viewModel.addSubtitles(setOf(subtitleData))
setSubtitles(selectedSubtitle)
viewModel.addSubtitles(subtitleData.toSet())
selectSourceDialog?.dismissSafe()
showToast(
String.format(ctx.getString(R.string.player_loaded_subtitles), subtitleData.name),
String.format(ctx.getString(R.string.player_loaded_subtitles), selectedSubtitle.name),
Toast.LENGTH_LONG
)
}
@ -913,10 +931,15 @@ class GeneratorPlayer : FullScreenPlayer() {
override fun playerError(exception: Throwable) {
Log.i(TAG, "playerError = $currentSelectedLink")
if (!hasNextMirror()) {
viewModel.forceClearCache = true
}
super.playerError(exception)
}
private fun noLinksFound() {
viewModel.forceClearCache = true
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
activity?.popCurrentPage()
}
@ -1383,6 +1406,7 @@ class GeneratorPlayer : FullScreenPlayer() {
}
binding?.playerLoadingGoBack?.setOnClickListener {
exitFullscreen()
player.release()
activity?.popCurrentPage()
}

View File

@ -45,6 +45,8 @@ class PlayerGeneratorViewModel : ViewModel() {
*/
private var currentLoadingEpisodeId: Int? = null
var forceClearCache = false
fun setSubtitleYear(year: Int?) {
_currentSubtitleYear.postValue(year)
}
@ -168,7 +170,7 @@ class PlayerGeneratorViewModel : ViewModel() {
}
}
fun loadLinks(clearCache: Boolean = false, type: LoadType = LoadType.InApp) {
fun loadLinks(type: LoadType = LoadType.InApp) {
Log.i(TAG, "loadLinks")
currentJob?.cancel()
@ -183,7 +185,7 @@ class PlayerGeneratorViewModel : ViewModel() {
// load more data
_loadingLinks.postValue(Resource.Loading())
val loadingState = safeApiCall {
generator?.generateLinks(type = type, clearCache = clearCache, callback = {
generator?.generateLinks(type = type, clearCache = forceClearCache, callback = {
currentLinks.add(it)
// Clone to prevent ConcurrentModificationException
normalSafeApiCall {

View File

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
import android.util.Log
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.result.ResultEpisode
@ -10,6 +11,12 @@ import com.lagradost.cloudstream3.utils.ExtractorUri
import kotlin.math.max
import kotlin.math.min
data class Cache(
val linkCache: MutableSet<ExtractorLink>,
val subtitleCache: MutableSet<SubtitleData>,
var lastCachedTimestamp: Long = unixTime
)
class RepoLinkGenerator(
private val episodes: List<ResultEpisode>,
private var currentIndex: Int = 0,
@ -17,7 +24,7 @@ class RepoLinkGenerator(
) : IGenerator {
companion object {
const val TAG = "RepoLink"
val cache: HashMap<Pair<String, Int>, Pair<MutableSet<ExtractorLink>, MutableSet<SubtitleData>>> =
val cache: HashMap<Pair<String, Int>, Cache> =
hashMapOf()
}
@ -76,10 +83,10 @@ class RepoLinkGenerator(
val index = currentIndex
val current = episodes.getOrNull(index + offset) ?: return false
val (currentLinkCache, currentSubsCache) = if (clearCache) {
Pair(mutableSetOf(), mutableSetOf())
val (currentLinkCache, currentSubsCache, lastCachedTimestamp) = if (clearCache) {
Cache(mutableSetOf(), mutableSetOf(), unixTime)
} else {
cache[current.apiName to current.id] ?: Pair(mutableSetOf(), mutableSetOf())
cache[current.apiName to current.id] ?: Cache(mutableSetOf(), mutableSetOf(), unixTime)
}
//val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet()
@ -89,6 +96,12 @@ class RepoLinkGenerator(
val currentSubsUrls = mutableSetOf<String>() // makes all subs urls unique
val currentSubsNames = mutableSetOf<String>() // makes all subs names unique
val invalidateCache = unixTime - lastCachedTimestamp > 60 * 20 // 20 minutes
if(invalidateCache){
currentLinkCache.clear()
currentSubsCache.clear()
}
currentLinkCache.filter { allowedTypes.contains(it.type) }.forEach { link ->
currentLinks.add(link.url)
callback(link to null)
@ -147,7 +160,7 @@ class RepoLinkGenerator(
}
}
)
cache[Pair(current.apiName, current.id)] = Pair(currentLinkCache, currentSubsCache)
cache[Pair(current.apiName, current.id)] = Cache(currentLinkCache, currentSubsCache, unixTime)
return result
}

View File

@ -2,6 +2,9 @@ package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Rect
@ -31,6 +34,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
@ -77,7 +81,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
open class ResultFragmentPhone : FullScreenPlayer() {
private val gestureRegionsListener = object : PanelsChildGestureRegionObserver.GestureRegionsListener {
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
@ -247,6 +250,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
}
var selectSeason: String? = null
var selectEpisodeRange: String? = null
private fun setUrl(url: String?) {
if (url == null) {
@ -751,6 +755,17 @@ open class ResultFragmentPhone : FullScreenPlayer() {
resultLoadingError.isVisible = data is Resource.Failure
resultErrorText.isVisible = data is Resource.Failure
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
resultTitle.setOnLongClickListener {
val titleToCopy = resultTitle.text
val clipboardManager =
activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
clipboardManager?.setPrimaryClip(ClipData.newPlainText("Title", titleToCopy))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
showToast(R.string.copyTitle, Toast.LENGTH_SHORT)
}
return@setOnLongClickListener true
}
}
}
@ -1027,6 +1042,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
observeNullable(viewModel.selectedRange) { range ->
resultBinding?.apply {
resultEpisodeSelect.setText(range)
selectEpisodeRange = range?.asStringNull(resultEpisodeSelect.context)
// If Season button is invisible then the bookmark button next focus is episode select
if (resultEpisodeSelect.isVisible && !resultSeasonButton.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultEpisodeSelect)
@ -1060,9 +1077,12 @@ open class ResultFragmentPhone : FullScreenPlayer() {
r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
}
view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
index to name
}) {
activity?.showDialog(
names.map { it.second },
names.indexOfFirst { it.second == selectEpisodeRange },
"",
false,
{}) { itemId ->
viewModel.changeRange(names[itemId].first)
}
}

View File

@ -1006,6 +1006,7 @@ class ResultViewModel2 : ViewModel() {
removeFavoritesData(currentId)
statusChangedCallback?.invoke(false)
_favoriteStatus.postValue(false)
MainActivity.reloadLibraryEvent(true)
} else {
checkAndWarnDuplicates(
context,
@ -1050,8 +1051,8 @@ class ResultViewModel2 : ViewModel() {
)
_favoriteStatus.postValue(true)
statusChangedCallback?.invoke(true)
MainActivity.reloadLibraryEvent(true)
}
}
}
@ -2604,6 +2605,11 @@ class ResultViewModel2 : ViewModel() {
this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating
this.tags = searchResponse.tags
}
if (searchResponse is DataStoreHelper.BookmarkedData) {
this.plot = searchResponse.plot
this.rating = searchResponse.rating
this.tags = searchResponse.tags
}
}
val mainId = searchResponse.id ?: response.getId()

View File

@ -264,6 +264,9 @@ class SearchFragment : Fragment() {
builder.setContentView(selectMainpageBinding.root)
builder.show()
builder.let { dialog ->
val previousSelectedApis = selectedApis.toSet()
val previousSelectedSearchTypes = selectedSearchTypes.toSet()
val isMultiLang = ctx.getApiProviderLangSettings().let { set ->
set.size > 1 || set.contains(AllLanguagesName)
}
@ -352,7 +355,9 @@ class SearchFragment : Fragment() {
selectedApis = currentSelectedApis
// run search when dialog is close
search(binding?.mainSearch?.query?.toString())
if(previousSelectedApis != selectedApis.toSet() || previousSelectedSearchTypes != selectedSearchTypes.toSet()) {
search(binding?.mainSearch?.query?.toString())
}
}
updateList(selectedSearchTypes.toList())
}

View File

@ -160,7 +160,7 @@ class PluginsViewModel : ViewModel() {
PluginManager.downloadPlugin(
activity,
metadata.url,
metadata.name,
metadata.internalName,
repo,
isEnabled
) to message
@ -257,4 +257,4 @@ class PluginsViewModel : ViewModel() {
false to downloadedPlugins.filterTvTypes().filterLang().sortByQuery(currentQuery)
)
}
}
}

View File

@ -1,10 +1,17 @@
package com.lagradost.cloudstream3.ui.settings.extensions
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
@ -112,6 +119,17 @@ class RepoAdapter(
repositoryItemRoot.setOnClickListener {
clickCallback(repositoryData)
}
repositoryItemRoot.setOnLongClickListener {
val clipboardManager =
activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
clipboardManager?.setPrimaryClip(ClipData.newPlainText("RepoUrl", repositoryData.url))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
showToast(R.string.copyRepoUrl, Toast.LENGTH_SHORT)
}
return@setOnLongClickListener true
}
mainText.text = repositoryData.name
subText.text = repositoryData.url
}

View File

@ -5,7 +5,7 @@ import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeyClass
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKeyClass
@ -51,7 +51,7 @@ class PreferenceDelegate<T : Any>(
}
object DataStore {
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
val mapper: JsonMapper = JsonMapper.builder().addModule(kotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
private fun getPreferences(context: Context): SharedPreferences {

View File

@ -3,18 +3,24 @@ package com.lagradost.cloudstream3.utils
class Event<T> {
private val observers = mutableSetOf<(T) -> Unit>()
val size : Int get() = observers.size
val size: Int get() = observers.size
operator fun plusAssign(observer: (T) -> Unit) {
observers.add(observer)
synchronized(observers) {
observers.add(observer)
}
}
operator fun minusAssign(observer: (T) -> Unit) {
observers.remove(observer)
synchronized(observers) {
observers.remove(observer)
}
}
operator fun invoke(value: T) {
for (observer in observers)
observer(value)
synchronized(observers) {
for (observer in observers)
observer(value)
}
}
}
}

View File

@ -96,13 +96,18 @@ import com.lagradost.cloudstream3.extractors.Moviesm4u
import com.lagradost.cloudstream3.extractors.Mp4Upload
import com.lagradost.cloudstream3.extractors.Mvidoo
import com.lagradost.cloudstream3.extractors.MwvnVizcloudInfo
import com.lagradost.cloudstream3.extractors.MyCloud
import com.lagradost.cloudstream3.extractors.Neonime7n
import com.lagradost.cloudstream3.extractors.Neonime8n
import com.lagradost.cloudstream3.extractors.Odnoklassniki
import com.lagradost.cloudstream3.extractors.TauVideo
import com.lagradost.cloudstream3.extractors.SibNet
import com.lagradost.cloudstream3.extractors.ContentX
import com.lagradost.cloudstream3.extractors.EmturbovidExtractor
import com.lagradost.cloudstream3.extractors.Hotlinger
import com.lagradost.cloudstream3.extractors.FourCX
import com.lagradost.cloudstream3.extractors.PlayRu
import com.lagradost.cloudstream3.extractors.FourPlayRu
import com.lagradost.cloudstream3.extractors.HDMomPlayer
import com.lagradost.cloudstream3.extractors.HDPlayerSystem
import com.lagradost.cloudstream3.extractors.VideoSeyred
@ -113,6 +118,7 @@ import com.lagradost.cloudstream3.extractors.TRsTX
import com.lagradost.cloudstream3.extractors.VidMoxy
import com.lagradost.cloudstream3.extractors.PixelDrain
import com.lagradost.cloudstream3.extractors.MailRu
import com.lagradost.cloudstream3.extractors.Mediafire
import com.lagradost.cloudstream3.extractors.OkRuSSL
import com.lagradost.cloudstream3.extractors.OkRuHTTP
import com.lagradost.cloudstream3.extractors.Okrulink
@ -150,6 +156,8 @@ import com.lagradost.cloudstream3.extractors.StreamSB8
import com.lagradost.cloudstream3.extractors.StreamSB9
import com.lagradost.cloudstream3.extractors.StreamTape
import com.lagradost.cloudstream3.extractors.StreamTapeNet
import com.lagradost.cloudstream3.extractors.StreamTapeXyz
import com.lagradost.cloudstream3.extractors.StreamWishExtractor
import com.lagradost.cloudstream3.extractors.StreamhideCom
import com.lagradost.cloudstream3.extractors.StreamhideTo
import com.lagradost.cloudstream3.extractors.Streamhub2
@ -178,10 +186,12 @@ import com.lagradost.cloudstream3.extractors.VideoVard
import com.lagradost.cloudstream3.extractors.VideovardSX
import com.lagradost.cloudstream3.extractors.Vidgomunime
import com.lagradost.cloudstream3.extractors.Vidgomunimesb
import com.lagradost.cloudstream3.extractors.VidhideExtractor
import com.lagradost.cloudstream3.extractors.Vidmoly
import com.lagradost.cloudstream3.extractors.Vidmolyme
import com.lagradost.cloudstream3.extractors.Vido
import com.lagradost.cloudstream3.extractors.Vidplay
import com.lagradost.cloudstream3.extractors.VidplayOnline
import com.lagradost.cloudstream3.extractors.Vidstreamz
import com.lagradost.cloudstream3.extractors.Vizcloud
import com.lagradost.cloudstream3.extractors.Vizcloud2
@ -210,6 +220,7 @@ import com.lagradost.cloudstream3.extractors.Ztreamhub
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import kotlinx.coroutines.delay
import me.xdrop.fuzzywuzzy.FuzzySearch
import org.jsoup.Jsoup
import java.net.URL
import java.util.UUID
@ -594,6 +605,18 @@ suspend fun loadExtractor(
}
}
// this is to match mirror domains - like example.com, example.net
for (extractor in extractorApis) {
if (FuzzySearch.partialRatio(
extractor.mainUrl,
currentUrl
) > 80
) {
extractor.getSafeUrl(currentUrl, referer, subtitleCallback, callback)
return true
}
}
return false
}
@ -617,6 +640,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
StreamTape(),
StreamTapeNet(),
ShaveTape(),
StreamTapeXyz(),
//mixdrop extractors
MixDropBz(),
@ -681,6 +705,9 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
SibNet(),
ContentX(),
Hotlinger(),
FourCX(),
PlayRu(),
FourPlayRu(),
HDMomPlayer(),
HDPlayerSystem(),
VideoSeyred(),
@ -813,6 +840,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Gdriveplayer(),
DatabaseGdrive(),
DatabaseGdrive2(),
Mediafire(),
YoutubeExtractor(),
YoutubeShortLinkExtractor(),
@ -824,6 +852,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
PlayLtXyz(),
AStreamHub(),
Vidplay(),
VidplayOnline(),
MyCloud(),
Cda(),
Dailymotion(),
@ -832,6 +862,9 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Rabbitstream(),
Dokicloud(),
Megacloud(),
VidhideExtractor(),
StreamWishExtractor(),
EmturbovidExtractor()
)

View File

@ -23,6 +23,7 @@ import okio.buffer
import okio.sink
import java.io.File
import android.text.TextUtils
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import java.io.BufferedReader
import java.io.IOException
@ -191,7 +192,7 @@ class InAppUpdater {
Update(
shouldUpdate,
foundAsset.browser_download_url,
tagResponse.github_object.sha,
tagResponse.github_object.sha.take(10),
found.body,
found.node_id
)
@ -213,7 +214,7 @@ class InAppUpdater {
this.cacheDir.listFiles()?.filter {
it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix
}?.forEach {
it.deleteOnExit()
deleteFileOnExit(it)
}
val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix")
@ -293,7 +294,13 @@ class InAppUpdater {
update.updateVersion
)
)
builder.setMessage("${update.changelog}")
val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)")
val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult ->
matchResult.groupValues[1]
} // Sanitized because it looks cluttered
builder.setMessage(sanitizedChangelog)
val context = this
builder.apply {

View File

@ -12,6 +12,7 @@ import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
@ -75,7 +76,7 @@ class PackageInstallerService : Service() {
this@PackageInstallerService.cacheDir.listFiles()?.filter {
it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix
}?.forEach {
it.deleteOnExit()
deleteFileOnExit(it)
}
}

View File

@ -35,6 +35,9 @@ import androidx.core.graphics.blue
import androidx.core.graphics.drawable.toBitmapOrNull
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginRight
@ -56,6 +59,7 @@ import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.google.android.material.chip.ChipGroup
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.UiImage
@ -86,8 +90,9 @@ object UIHelper {
if (view == null) return
view.removeAllViews()
val context = view.context ?: return
val maxTags = tags.take(10) // Limited because they are too much
tags.forEach { tag ->
maxTags.forEach { tag ->
val chip = Chip(context)
val chipDrawable = ChipDrawable.createFromAttributes(
context,
@ -179,9 +184,7 @@ object UIHelper {
try {
if (this is FragmentActivity) {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?
navHostFragment?.navController?.let {
it.navigate(navigation, arguments)
}
navHostFragment?.navController?.navigate(navigation, arguments)
}
} catch (t: Throwable) {
logError(t)
@ -401,81 +404,34 @@ object UIHelper {
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
// or View.SYSTEM_UI_FLAG_LOW_PROFILE
)
// window.addFlags(View.KEEP_SCREEN_ON)
/** BUGGED AF **/
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, View(this)).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}*/
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
)
//}
}
fun FragmentActivity.popCurrentPage() {
this.onBackPressedDispatcher.onBackPressed()
/*val currentFragment = supportFragmentManager.fragments.lastOrNull {
it.isVisible
} ?: return
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit
)
.remove(currentFragment)
.commitAllowingStateLoss()*/
}
/*
fun FragmentActivity.popCurrentPage(isInPlayer: Boolean, isInExpandedView: Boolean, isInResults: Boolean) {
val currentFragment = supportFragmentManager.fragments.lastOrNull {
it.isVisible
}
?: //this.onBackPressedDispatcher.onBackPressed()
return
/*
if (tvActivity == null) {
requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) {
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}*/
// No fucked animations leaving the player :)
when {
isInPlayer -> {
supportFragmentManager.beginTransaction()
//.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit)
.remove(currentFragment)
.commitAllowingStateLoss()
}
isInExpandedView && !isInResults -> {
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,//R.anim.enter_from_right,
R.anim.exit_anim,//R.anim.exit_to_right,
R.anim.pop_enter,
R.anim.pop_exit
)
.remove(currentFragment)
.commitAllowingStateLoss()
}
else -> {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
.remove(currentFragment)
.commitAllowingStateLoss()
}
}
}*/
fun Context.getStatusBarHeight(): Int {
if (isTvSettings()) {
@ -542,13 +498,26 @@ object UIHelper {
fun Activity.changeStatusBarState(hide: Boolean): Int {
return if (hide) {
window?.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else {
@Suppress("DEPRECATION")
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
}
0
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.show(WindowInsets.Type.statusBars())
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
this.getStatusBarHeight()
}
}
@ -556,13 +525,19 @@ object UIHelper {
// Shows the system bars by removing all the flags
// except for the ones that make the content appear under the system bars.
fun Activity.showSystemUI() {
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowInsetsControllerCompat(window, View(this)).show(WindowInsetsCompat.Type.systemBars())
} else {*/ /** WINDOW COMPAT IS BUGGY DUE TO FU*KED UP PLAYER AND TRAILERS **/
Suppress("DEPRECATION")
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
//}
changeStatusBarState(isEmulatorSettings())
// window.clearFlags(View.KEEP_SCREEN_ON)
}
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {

View File

@ -41,17 +41,36 @@
android:layout_marginStart="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/resultview_preview_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/textColor"
android:textSize="16sp"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:textStyle="bold"
tools:text="The Perfect Run">
<TextView
android:id="@+id/resultview_preview_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/textColor"
android:textSize="16sp"
android:layout_gravity="start|center_vertical"
android:textStyle="bold"
android:layout_marginEnd="25dp"
tools:text="The Perfect Run">
</TextView>
</TextView>
<ImageView
android:id="@+id/resultview_preview_favorite"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="5dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:elevation="10dp"
android:nextFocusDown="@id/resultview_preview_bookmark"
android:src="@drawable/ic_baseline_favorite_border_24"
app:tint="?attr/textColor" />
</FrameLayout>
<com.lagradost.cloudstream3.widget.FlowLayout
android:layout_width="match_parent"
@ -123,6 +142,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/resultview_preview_bookmark"
android:layout_weight="1"
android:nextFocusUp="@id/resultview_preview_favorite"
android:nextFocusRight="@id/resultview_preview_more_info"
tools:visibility="visible"
@ -136,6 +156,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/resultview_preview_more_info"
android:layout_weight="1"
android:nextFocusUp="@id/resultview_preview_favorite"
android:nextFocusLeft="@id/resultview_preview_bookmark"
tools:visibility="visible"

View File

@ -137,7 +137,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="5"
android:maxLines="3"
android:paddingBottom="5dp"
android:textSize="15sp"
tools:text="very nice tv series" />

View File

@ -159,6 +159,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="40dp">
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/library_random"
style="@style/ExtendedFloatingActionButton"
android:layout_gravity="bottom|start"
android:text="@string/home_random"
android:textColor="?attr/textColor"
android:visibility="gone"
app:icon="@drawable/ic_baseline_play_arrow_24"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/sort_fab"

View File

@ -190,6 +190,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="40dp">
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/library_random"
style="@style/ExtendedFloatingActionButton"
android:layout_gravity="bottom|start"
android:text="@string/home_random"
android:textColor="?attr/textColor"
android:visibility="gone"
app:icon="@drawable/ic_baseline_play_arrow_24"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/sort_fab"

View File

@ -83,7 +83,7 @@
android:layout_height="25dp"
android:layout_margin="5dp"
android:elevation="10dp"
android:tooltipText="@string/subscribe_tooltip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_notifications_none_24"
android:layout_gravity="end|center_vertical"
@ -100,7 +100,7 @@
android:layout_height="25dp"
android:layout_margin="5dp"
android:elevation="10dp"
android:tooltipText="@string/action_add_to_favorites"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_favorite_border_24"
android:layout_gravity="end|center_vertical"
@ -117,7 +117,7 @@
android:layout_height="25dp"
android:layout_margin="5dp"
android:elevation="10dp"
android:tooltipText="@string/result_share"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_outline_share_24"
android:layout_gravity="end|center_vertical"
@ -135,7 +135,7 @@
android:layout_height="25dp"
android:layout_margin="5dp"
android:elevation="10dp"
android:tooltipText="@string/result_open_in_browser"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_public_24"
android:layout_gravity="end|center_vertical"
@ -153,7 +153,7 @@
android:layout_height="30dp"
android:layout_margin="5dp"
android:elevation="10dp"
android:tooltipText="@string/result_search_tooltip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/search_icon"
android:layout_gravity="end|center_vertical"
@ -171,7 +171,7 @@
android:layout_height="25dp"
android:layout_margin="5dp"
android:elevation="10dp"
android:tooltipText="@string/recommendations_tooltip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_list_alt_24"
android:layout_gravity="end|center_vertical"

View File

@ -387,6 +387,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
<com.lagradost.cloudstream3.widget.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
app:itemSpacing="10dp">
<com.google.android.material.button.MaterialButton
@ -399,6 +400,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:id="@+id/result_meta_content_rating"
android:layout_gravity="center_vertical"
style="@style/SmallWhiteButton"
android:focusable="false"
tools:text="PG-13" />
<TextView

View File

@ -518,6 +518,26 @@
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent"
tools:text="23:20" />
<TextView
android:id="@+id/time_left"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center|center_vertical"
android:layout_marginEnd="20dp"
android:includeFontPadding="false"
android:minWidth="50dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent"
tools:text="-23:20"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<HorizontalScrollView

View File

@ -607,6 +607,25 @@
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent"
tools:text="23:20" />
<TextView
android:id="@+id/time_left"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center|center_vertical"
android:layout_marginEnd="20dp"
android:includeFontPadding="false"
android:minWidth="50dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent"
tools:text="-23:20"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -506,6 +506,25 @@
app:layout_constraintEnd_toEndOf="@id/player_fullscreen"
tools:text="23:20" />
<TextView
android:id="@+id/time_left"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center|center_vertical"
android:layout_marginEnd="30dp"
android:includeFontPadding="false"
android:minWidth="50dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="@id/player_fullscreen"
tools:text="-23:20"
android:visibility="gone"/>
<ImageView
android:id="@+id/player_fullscreen"
android:layout_width="30dp"

View File

@ -210,7 +210,7 @@
<string name="episode_action_play_in_format">مشي بـ\"%s\"</string>
<string name="episode_action_download_subtitle">نزل الترجمة</string>
<string name="dont_show_again">ما تفرجيا بعد مرة</string>
<string name="video_buffer_clear_settings">فضّي التخزين المؤقت للصور والڤيديوات</string>
<string name="video_buffer_clear_settings">فضّي التخزين الموقت للصور والڤيديوات</string>
<string name="video_skip_op">أفي المقدمة</string>
<string name="watch_quality_pref">الجودة المفضلة (Wi-Fi)</string>
<string name="cartoons_singular">رسوم مُتَحَرِكة</string>
@ -260,7 +260,7 @@
<string name="add_site_pref">نسوخ موقع (clone site)</string>
<string name="random_button_settings">زر العشوائي</string>
<string name="resize_fill">مدو</string>
<string name="random_button_settings_desc">فرجي زر \"عشوائي\" على الصفحة الرئيسية</string>
<string name="random_button_settings_desc">فرجي زر \"عشوائي\" بالصفحة الرئيسية وبصفحة الرفّ</string>
<string name="pref_category_ui_features">الميزات</string>
<string name="display_subbed_dubbed_settings">فرجي أنمي المدبلج-المترجم</string>
<string name="pref_category_backup">النسخ الإحتياطي</string>
@ -595,4 +595,5 @@
<string name="rotate_video">برومو</string>
<string name="auto_rotate_video_desc">غير إتجاه الشاشة أوتوماتيكيًا حسب شكل الڤيديو</string>
<string name="links_reloaded_toast">رجع نعمل لاود لاللينك</string>
<string name="copyTitle">نعمل كَپي للعنوان!</string>
</resources>

View File

@ -283,7 +283,7 @@
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
<string name="category_general">عام</string>
<string name="random_button_settings">زر العشوائي</string>
<string name="random_button_settings_desc">إظهار زر العشوائي على الصفحة الرئيسية</string>
<string name="random_button_settings_desc">إظهار زر عشوائي على الصفحة الرئيسية والمكتبة</string>
<string name="provider_lang_settings">لغات المزود</string>
<string name="app_layout">واجهة التطبيق</string>
<string name="preferred_media_settings">المحتوى المفضل</string>
@ -618,9 +618,10 @@
<string name="edit_account">تعديل الحساب</string>
<string name="links_reloaded_toast">تم إعادة تحميل الروابط</string>
<string name="rotate_video_desc">عرض زر تبديل لاتجاه الشاشة</string>
<string name="rotate_video_key">مفتاح تدوير الفيديو</string>
<string name="rotate_video_key">تدوير الفيديو</string>
<string name="auto_rotate_video_key">مفتاح تدوير الفيديو التلقائي</string>
<string name="auto_rotate_video">الدوران التلقائي</string>
<string name="rotate_video">تدوير</string>
<string name="auto_rotate_video_desc">تمكين التبديل التلقائي لاتجاه الشاشة بناءً على اتجاه الفيديو</string>
<string name="copyTitle">تم نسخ العنوان!</string>
</resources>

View File

@ -122,7 +122,7 @@
<string name="search">অনুসন্ধান করুন</string>
<string name="category_account">অ্যাকাউন্টসমূহ</string>
<string name="bug_report_settings_on">কোনো উপাত্ত পাঠাবে না</string>
<string name="double_tap_to_pause_settings_des">বিরতি দিতে মাঝে চাপুন</string>
<string name="double_tap_to_pause_settings_des">বিরতি দিতে মাঝে দুইবার চাপুন</string>
<string name="use_system_brightness_settings">সিস্টেম এর উজ্জ্বলতা ব্যবহার করুন</string>
<string name="play_trailer_button">ট্রেইলার চালু করুন</string>
<string name="swipe_to_seek_settings_des">ভিডিওর সময় নিয়ন্ত্রণ করতে, ডানে অথবা বামে সোয়াইপ করুন</string>
@ -148,4 +148,86 @@
<string name="update_started">আপডেট শুরু হয়েছে</string>
<string name="browser">ব্রাউজার</string>
<string name="test_log">লগ</string>
<string name="double_tap_to_seek_amount_settings">প্লেয়ারে এগিয়ে যাওয়ার পরিমাণ (সেকেন্ডে)</string>
<string name="double_tap_to_seek_settings_des">সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন</string>
<string name="delete_file">ফাইল ডিলিট</string>
<string name="acra_report_toast">দুঃখিত, অ্যাপ্লিকেশন ক্র্যাশ হয়েছে। ডেভেলপারদের কাছে একটি বেনামী বাগ রিপোর্ট পাঠানো হবে</string>
<string name="bug_report_settings_off">শুধুমাত্র ক্র্যাশ এর তথ্য পাঠায়</string>
<string name="subs_default_reset_toast">মান ডিফল্ট এ রিসেট করুন</string>
<string name="uprereleases_settings_des">ফুল রিলিজের পরিবর্তে শুধুমাত্র প্রি-রিলিজ আপডেটের জন্য অনুসন্ধান করুন</string>
<string name="updates_settings_des">স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন</string>
<string name="movies_singular">সিনেমা</string>
<string name="show_fillers_settings">এনিমে এর ফিলার পর্ব দেখায়</string>
<string name="tv_series">টিভি সিরিজ</string>
<string name="no_links_found_toast">লিংক পাওয়া যায়নি</string>
<string name="benene_des">চা খাওয়ানো হয়েছে</string>
<string name="go_forward_30">+৩০</string>
<string name="movies">সিনেমা</string>
<string name="discord">ডিসকর্ডে যোগ দিন</string>
<string name="torrent">টরেন্টস</string>
<string name="delete_message" formatted="true">এটি স্থায়ীভাবে মুছে ফেলা হবে %s
\nআপনি কি নিশ্চিত?</string>
<string name="pause">থামুন</string>
<string name="go_back_30">-৩০</string>
<string name="github">গিটহাব</string>
<string name="free_storage">ফ্রি</string>
<string name="backup_settings">ডেটা ব্যাকআপ করুন</string>
<string name="restore_success">ব্যাকআপ ফাইল লোড হয়েছে</string>
<string name="year">বছর</string>
<string name="no_season">কোন সিজন নেই</string>
<string name="play_episode_toast">এপিসোড পরলে করুন</string>
<string name="resume">শুরু করুন</string>
<string name="documentaries">তথ্যচিত্র</string>
<string name="automatic_plugin_download_summary">যোগ করা রিপোজিটরি থেকে এখনও ইনস্টল করা হয়নি এমন সব প্লাগইন স্বয়ংক্রিয়ভাবে ইনস্টল করুন।</string>
<string name="delete">ডিলিট</string>
<string name="start">শুরু</string>
<string name="cartoons">কার্টুন</string>
<string name="apk_installer_settings_des">কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন।</string>
<string name="no_subtitles">সাবটাইটেল নেই</string>
<string name="no_chromecast_support_toast">এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না</string>
<string name="advanced_search">উন্নত অনুসন্ধান</string>
<string name="synopsis">সারমর্ম</string>
<string name="used_storage">ব্যবহৃত</string>
<string name="library">লাইব্রেরী</string>
<string name="lightnovel">আমাদের তৈরি ছোট উপন্যাস পড়ার অ্যাপ্লিকেশন</string>
<string name="resume_time_left" formatted="true">%d মি
\nবাকি</string>
<string name="others">অন্যান্য</string>
<string name="status_ongoing">চলমান</string>
<string name="asian_drama">এশিয়ান নাটক</string>
<string name="queued">অপেক্ষা করছে</string>
<string name="episode">পর্ব</string>
<string name="status">অবস্থা</string>
<string name="use_system_brightness_settings_des">অ্যাপের প্লেয়ারে অন্ধকার ওভারলে এর পরিবর্তে সিস্টেম ব্রাইটনেস ব্যবহার করুন</string>
<string name="restore_failed_format" formatted="true">%s ফাইল থেকে ডেটা পুনরুদ্ধার করা ব্যর্থ হয়েছে</string>
<string name="no_episodes_found">কোনো এপিসোড পাওয়া যায়নি</string>
<string name="test_failed">ব্যর্থ হয়েছে</string>
<string name="advanced_search_des">প্রোভাইডার অনুযায়ী আপনাকে পৃথক অনুসন্ধান ফলাফল দেয়</string>
<string name="tv_series_singular">সিরিজ</string>
<string name="rating">রেটিং</string>
<string name="uprereleases_settings">প্রি-রিলিজে আপডেট করুন</string>
<string name="redo_setup_process">সেটআপ প্রক্রিয়া পুনরায় করুন</string>
<string name="benene">ভাই এক কাপ চা খাওয়াও ☕</string>
<string name="restore_settings">ব্যাকআপ থেকে ডেটা পুনরুদ্ধার করুন</string>
<string name="copy_link_toast">ক্লিপবোর্ডে লিঙ্ক কপি করা হয়েছে</string>
<string name="status_completed">দেখা শেষ</string>
<string name="anim">আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন</string>
<string name="nsfw">18+</string>
<string name="anime">এনিমে</string>
<string name="pref_filter_search_quality">অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান</string>
<string name="app_storage">অ্যাপ</string>
<string name="livestreams">লাইভস্ট্রিম</string>
<string name="apk_installer_settings">APK ইনস্টলার</string>
<string name="duration">সময়কাল</string>
<string name="app_language">অ্যাপের ভাষা</string>
<string name="test_passed">পাস করেছে</string>
<string name="episodes">পর্ব</string>
<string name="backup_failed_error_format">%s ব্যাক আপ করতে সমস্যা হয়েছে</string>
<string name="site">সাইট</string>
<string name="season">সিজন</string>
<string name="backup_frequency">ব্যাকআপ ফ্রিকোয়েন্সি</string>
<string name="episode_sync_settings">দেখার অগ্রগতি আপডেট করুন</string>
<string name="episode_sync_settings_des">আপনার বর্তমান পর্বের অগ্রগতি স্বয়ংক্রিয়ভাবে সিঙ্ক করুন</string>
<string name="automatic_plugin_download_mode_title">প্লাগইন ডাউনলোড ফিল্টার করতে মোড নির্বাচন করুন</string>
<string name="links_reloaded_toast">লিঙ্ক পুনরায় লোড হয়েছে</string>
</resources>

View File

@ -265,7 +265,7 @@
<string name="legal_notice_text" translatable="false">Jakékoli právní otázky týkající se obsahu této aplikace je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. Aplikace je určena výhradně pro vzdělávací a osobní účely. CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, uživatelsky přívětivém rozhraní. Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte CloudStream 3 na vlastní nebezpečí.</string>
<string name="category_general">Obecné</string>
<string name="random_button_settings">Náhodné tlačítko</string>
<string name="random_button_settings_desc">Zobrazit na domovské stránce náhodné tlačítko</string>
<string name="random_button_settings_desc">Zobrazit na domovské stránce a v knihovně náhodné tlačítko</string>
<string name="provider_lang_settings">Jazyk poskytovatelů</string>
<string name="app_layout">Rozložení aplikace</string>
<string name="preferred_media_settings">Preferovaná média</string>
@ -615,4 +615,5 @@
<string name="auto_rotate_video">Automatické otáčení</string>
<string name="rotate_video">Otočení</string>
<string name="auto_rotate_video_desc">Zapnout automatické otáčení obrazovky v závislosti na orientaci videa</string>
<string name="copyTitle">Název zkopírován!</string>
</resources>

View File

@ -585,4 +585,8 @@
\n
\nWollen sie dieses Element trotzdem hinzufügen, das existierende ersetzen oder diese Aktion abbrechen?</string>
<string name="links_reloaded_toast">Links wurden neu geladen</string>
<string name="rotate_video">Drehen</string>
<string name="rotate_video_desc">Zeige einen Umschalter für Bildschirmorientierung an</string>
<string name="auto_rotate_video">Automatisch drehen</string>
<string name="copyTitle">Titel kopiert!</string>
</resources>

View File

@ -305,7 +305,7 @@
<string name="pref_category_looks">Aspecto</string>
<string name="pref_category_ui_features">Características</string>
<string name="random_button_settings">Botón aleatorio</string>
<string name="random_button_settings_desc">Mostrar el botón aleatorio en la página de inicio</string>
<string name="random_button_settings_desc">Mostrar el botón aleatorio en la página de inicio y en la biblioteca</string>
<string name="account">Cuenta</string>
<string name="logout">Cerrar sesión</string>
<string name="switch_account">Cambiar cuenta</string>
@ -591,4 +591,5 @@
<string name="auto_rotate_video">Giro automático</string>
<string name="rotate_video">Girar</string>
<string name="auto_rotate_video_desc">Activar el cambio automático de la orientación de la pantalla en función de la orientación del vídeo</string>
<string name="copyTitle">¡Título copiado!</string>
</resources>

View File

@ -53,4 +53,38 @@
<string name="torrent_plot">شرح</string>
<string name="subs_subtitle_languages">زبان زیرنویس</string>
<string name="player_subtitles_settings">زیرنویس</string>
<string name="action_remove_from_bookmarks">حذف</string>
<string name="download_started">بارگیری آغاز شد</string>
<string name="pref_disable_acra">غیرفعال کردن گذارش باگ خودکار</string>
<string name="sort_clear">پاک کردن</string>
<string name="update_started">به‌روزرسانی آغاز شد</string>
<string name="sort_copy">کپی</string>
<string name="home_more_info">اطلاعات بیشتر</string>
<string name="subs_auto_select_language">انتخاب زبان خودکار</string>
<string name="continue_watching">ادامهٔ تماشا</string>
<string name="subs_download_languages">بارگیری زبان‌ها</string>
<string name="subs_background_color">زنگ پس‌زمینه</string>
<string name="popup_pause_download">توقف بارگیری</string>
<string name="app_dubbed_text">دوبله</string>
<string name="torrent_no_plot">شرحی یافت نشد</string>
<string name="subtitles_settings">تنظیمات زیرنویس</string>
<string name="subs_window_color">رنگ پنجره</string>
<string name="eigengraumode_settings_des">افزودن گرینهٔ سرعت در پخش‌کننده</string>
<string name="download_canceled">بارگیری لغو شد</string>
<string name="home_expanded_hide">پنهان کردن</string>
<string name="sort_apply">اعمال کردن</string>
<string name="normal_no_plot">پیرنگی یافت نشد</string>
<string name="download_paused">بارگیری متوقف شد</string>
<string name="popup_delete_file">حذف فایل</string>
<string name="downloaded">بارگرفتن</string>
<string name="popup_resume_download">ازسرگیری بارگیری</string>
<string name="subs_text_color">رنگ متن</string>
<string name="popup_play_file">نمایش فایل</string>
<string name="go_back">بازگشت</string>
<string name="sort_save">ذخیره</string>
<string name="home_play">نمایش</string>
<string name="autoplay_next_settings">پخش خودکار قسمت بعد</string>
<string name="picture_in_picture">تصویر در تصویر</string>
<string name="sort_close">بستن</string>
<string name="home_next_random_img_des">اتاق بعدی</string>
</resources>

View File

@ -94,7 +94,7 @@
<string name="sort_copy">Kopiraj</string>
<string name="sort_close">Zatvori</string>
<string name="sort_clear">Očisti</string>
<string name="sort_save">Sačuvaj</string>
<string name="sort_save">Spremi</string>
<string name="player_speed">Brzina playera</string>
<string name="subtitles_settings">Postavke titlova</string>
<string name="subs_text_color">Boja teksta</string>
@ -298,7 +298,7 @@
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
<string name="category_general">Općenito</string>
<string name="random_button_settings">Random gumb</string>
<string name="random_button_settings_desc">Prikaži gumb za slučajni odabir reprodukcija na početnoj stranici</string>
<string name="random_button_settings_desc">Prikaži gumb za slučajni odabir reprodukcija na početnoj stranici i biblioteci</string>
<string name="provider_lang_settings">Jezici pružatelja usluga</string>
<string name="app_layout">Izgled aplikacije</string>
<string name="preferred_media_settings">Preferirani mediji</string>
@ -609,4 +609,12 @@
<string name="skip_startup_account_select_pref">Preskoči odabir računa pri pokretanju</string>
<string name="manage_accounts">Upravljanje računima</string>
<string name="edit_account">Uredi račun</string>
<string name="links_reloaded_toast">Linkovi ponovno učitani</string>
<string name="rotate_video">Rotiraj</string>
<string name="rotate_video_desc">Prikaži gumb za prebacivanje orijentacije zaslona</string>
<string name="auto_rotate_video_desc">Omogućuje automatsko mijenjanje orijentacije zaslona na temelju orijentacije videa</string>
<string name="auto_rotate_video">Automatsko rotiranje</string>
<string name="copyTitle">Naslov je kopiran!</string>
<string name="rotate_video_key">rotiraj_video_tipka</string>
<string name="auto_rotate_video_key">automatski_rotiraj_video_tipka</string>
</resources>

View File

@ -219,4 +219,27 @@
<string name="subs_auto_select_language">言語の自動選択</string>
<string name="error_loading_links_toast">リンクの読み込みエラー</string>
<string name="enter_pin">137905</string>
<string name="links_reloaded_toast">リンクがリロードされました</string>
<string name="pref_disable_acra">自動バグ報告を無効にする</string>
<string name="player_speed">プレーヤーの速度</string>
<string name="subtitles_settings">字幕設定</string>
<string name="app_dubbed_text">ダブ</string>
<string name="app_subbed_text">サブ</string>
<string name="torrent_no_plot">説明が見つかりません</string>
<string name="swipe_to_change_settings">スワイプして設定を変更します</string>
<string name="subs_outline_color">輪郭の色</string>
<string name="subs_subtitle_elevation">字幕の標高</string>
<string name="benene_count_text">%d ベネネスが開発者に与えられました</string>
<string name="vpn_might_be_needed">このプロバイダーが正しく動作するには VPN が必要になる可能性があります</string>
<string name="vpn_torrent">このプロバイダーは torrent です、VPN をお勧めします</string>
<string name="player_size_settings">プレーヤーのサイズ変更ボタン</string>
<string name="player_size_settings_des">黒い境界線を削除します</string>
<string name="player_subtitles_settings_des">プレーヤーの字幕設定</string>
<string name="chromecast_subtitles_settings">Chromecastの字幕</string>
<string name="chromecast_subtitles_settings_des">Chromecastの字幕設定</string>
<string name="eigengraumode_settings_des">プレーヤーに速度オプションを追加します</string>
<string name="swipe_to_seek_settings">スワイプして探す</string>
<string name="autoplay_next_settings">次のエピソードを自動再生する</string>
<string name="autoplay_next_settings_des">現在のエピソードが終了したら次のエピソードを開始する</string>
<string name="subs_hold_to_reset_to_default">長押しするとデフォルトにリセットされます</string>
</resources>

View File

@ -171,7 +171,7 @@
<string name="action_mark_as_watched">കണ്ടതാണെന്ന് അടയാളപ്പെടുത്തുക</string>
<string name="next_episode_time_day_format" formatted="true">%1$d%2$d</string>
<string name="next_episode_format" formatted="true">yg5t4r%dujyhtg</string>
<string name="next_episode_time_hour_format" formatted="true">qeWERT</string>
<string name="next_episode_time_hour_format" formatted="true">%d മണിക്കൂർ %d മിനിറ്റ്</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%1$sghj%2$d</string>
<string name="cast_format" formatted="true">rtf:%</string>
<string name="create_account">അക്കൗണ്ട് ഉണ്ടാക്കുക</string>
@ -199,4 +199,52 @@
<string name="browser">ബ്രൗസർ</string>
<string name="type_re_watching">വീണ്ടും കാണുക</string>
<string name="stream">സ്ട്രീം</string>
<string name="subs_import_text" formatted="true">%s ൽ ഫോൻ്റ്‌സ് വെച്ചു കൊണ്ട് ഇംപോർട്ട് ചെയ്യുക</string>
<string name="safe_mode_description">പ്രശ്‌നമുണ്ടാക്കുന്ന ഒന്ന് കണ്ടെത്താൻ നിങ്ങളെ സഹായിക്കുന്നതിന് ഒരു ക്രാഷ് കാരണം എല്ലാ വിപുലീകരണങ്ങളും ഓഫാക്കി.</string>
<string name="view_public_repositories_button_short">പൊതു പട്ടിക</string>
<string name="blank_repo_message">CloudStream-ന് സ്ഥിരസ്ഥിതിയായി സൈറ്റുകളൊന്നും ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. നിങ്ങൾ റിപ്പോസിറ്ററികളിൽ നിന്ന് സൈറ്റുകൾ ഇൻസ്റ്റാൾ ചെയ്യേണ്ടതുണ്ട്.
\n
\nസ്കൈ യുകെ ലിമിറ്റഡിലെ ഡോഗ്‌ഷിറ്റ് ആളുകളിൽ നിന്ന് DMCA നീക്കം ചെയ്‌തതിനാൽ 🤮 ഞങ്ങൾക്ക് ആപ്പിൽ റിപ്പോസിറ്ററി സൈറ്റ് ലിങ്ക് ചെയ്യാൻ കഴിയില്ല.
\n
\nഞങ്ങളുടെ ഡിസ്കോർഡിൽ ചേരുക അല്ലെങ്കിൽ ഓൺലൈനിൽ തിരയുക.</string>
<string name="sort_copy">പകർത്തുക</string>
<string name="uppercase_all_subtitles">എല്ലാ സബ്‌ടൈറ്റിലുകളും വലിയക്ഷരമാക്കുക</string>
<string name="render_error">റെൻഡറർ പിശക്</string>
<string name="lock_profile">പ്രൊഫൈൽ ലോക്ക് ചെയ്യുക</string>
<string name="view_public_repositories_button">കമ്മ്യൂണിറ്റി റിപ്പോസിറ്ററികൾ കാണുക</string>
<string name="safe_mode_title">സുരക്ഷിത മോഡ് ഓണാണ്</string>
<string name="manage_accounts">അക്കൗണ്ടുകൾ കൈകാര്യം ചെയ്യുക</string>
<string name="coming_soon">ഉടൻ വരുന്നു…</string>
<string name="apply_on_restart">പുനരാരംഭിക്കുമ്പോൾ പ്രയോഗിക്കുക</string>
<string name="edit_account">അക്കൗണ്ട് എഡിറ്റ് ചെയ്യുക</string>
<string name="pin_error_incorrect">തെറ്റായ പിൻ. ദയവായി വീണ്ടും ശ്രമിക്കുക.</string>
<string name="stop">നിർത്തുക</string>
<string name="tracks">ട്രാക്കുകൾ</string>
<string name="pin_error_length">പിൻ 4 പ്രതീകങ്ങൾ ആയിരിക്കണം</string>
<string name="unexpected_error">അപ്രതീക്ഷിത പ്ലെയർ പിശക്</string>
<string name="previous">മുമ്പത്തെ</string>
<string name="delete_repository_plugins">ഇത് എല്ലാ റിപ്പോസിറ്ററി പ്ലഗിന്നുകളും ഇല്ലാതാക്കും</string>
<string name="restart">പുനരാരംഭിക്കുക</string>
<string name="safe_mode_crash_info">ക്രാഷ് വിവരം കാണുക</string>
<string name="provider_languages_tip">ഈ ഭാഷകളിലെ വീഡിയോകൾ കാണുക</string>
<string name="select_an_account">ഒരു അക്കൗണ്ട് തിരഞ്ഞെടുക്കുക</string>
<string name="home_source">ഉറവിടം</string>
<string name="video_tracks">വീഡിയോ ട്രാക്കുകൾ</string>
<string name="episode_action_chromecast_mirror">Chromecast മിറർ</string>
<string name="download_all_plugins_from_repo">ഈ ശേഖരത്തിൽ നിന്ന് എല്ലാ പ്ലഗിന്നുകളും ഡൗൺലോഡ് ചെയ്യണോ?</string>
<string name="delete_repository">ശേഖരം ഇല്ലാതാക്കുക</string>
<string name="home_random">ക്രമരഹിതം</string>
<string name="quality_cam">ക്യാമറ</string>
<string name="storage_error">ഡൗൺലോഡ് പിശക്, സംഭരണ അനുമതികൾ പരിശോധിക്കുക</string>
<string name="remote_error">റിമോട്ട് പിശക്</string>
<string name="episode_action_chromecast_episode">Chromecast എപ്പിസോഡ്</string>
<string name="setup_extensions_subtext">നിങ്ങൾ ഉപയോഗിക്കാൻ ആഗ്രഹിക്കുന്ന സൈറ്റുകളുടെ ലിസ്റ്റ് ഡൗൺലോഡ് ചെയ്യുക</string>
<string name="enter_pin">പിൻ നൽകുക</string>
<string name="sort_close">അടയ്ക്കുക</string>
<string name="action_add_to_bookmarks">വാച്ച് സ്റ്റാറ്റസ് സജ്ജീകരിക്കുക</string>
<string name="pin">പിൻ</string>
<string name="links_reloaded_toast">ലിങ്കുകൾ വീണ്ടും ലോഡുചെയ്‌തു</string>
<string name="source_error">ഉറവിട പിശക്</string>
<string name="enter_current_pin">നിലവിലെ പിൻ നൽകുക</string>
<string name="audio_tracks">ഓഡിയോ ട്രാക്കുകൾ</string>
</resources>

View File

@ -1,2 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<resources/>
<resources>
<string name="player_speed_text_format" formatted="true">गति (%.2fx)</string>
<string name="home_next_random_img_des">अर्को अनियमित</string>
<string name="preview_background_img_des">पृष्ठभूमि देखाउनुहोस्</string>
<string name="next_episode_time_hour_format" formatted="true">%1$dh %2$dm</string>
<string name="search_poster_img_des">पोस्टर</string>
<string name="go_back_img_des">पछाडी जाउ</string>
<string name="episode_poster_img_des">एपिसोडको पोस्टर</string>
<string name="home_change_provider_img_des">प्रोवाइडर परिवर्तन गर्नुहोस्</string>
<string name="rated_format" formatted="true">रेटिंग: %.1f</string>
<string name="next_episode_time_day_format" formatted="true">%1$dd %2$dh %3$dm</string>
<string name="next_episode_time_min_format" formatted="true">%dm</string>
<string name="result_poster_img_des">विज्ञापन</string>
<string name="home_main_poster_img_des">मुख्य पोस्टर</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s Ep %2$d</string>
<string name="cast_format" formatted="true">अभिनेता:%s</string>
<string name="new_update_format" formatted="true">नयाँ अपडेट भेटियो!
\n%1$s -&gt; %2$s</string>
<string name="filler" formatted="true">फिलर</string>
<string name="duration_format" formatted="true">%d मिनेट</string>
<string name="app_name">क्लाउडस्ट्रीम</string>
<string name="play_with_app_name">क्लाउडस्ट्रीममा प्ले गर्नुहोस्</string>
<string name="title_home">होम्</string>
<string name="title_search">खोजी</string>
<string name="title_downloads">डाउनलोडस</string>
<string name="title_settings">सेटिङह</string>
<string name="search_hint">खोज्नुहोस्…</string>
<string name="search_hint_site" formatted="true">%s खोज्नुहोस्…</string>
<string name="episode_more_options_des">थप विकल्पहरू</string>
<string name="result_tags">विधाहरू</string>
<string name="result_share">सेयर गर्नुहोस्</string>
<string name="result_open_in_browser">ब्राउजरमा खाेल्नुहाेस्</string>
<string name="browser">ब्राउजर</string>
<string name="skip_loading">लोड गर्न छोड्नुहोस्</string>
<string name="loading">लोड हुँदै…</string>
<string name="type_on_hold">होल्डमा भएको</string>
<string name="type_dropped">अधुरै छाडेको</string>
<string name="type_plan_to_watch">हेर्ने योजना</string>
<string name="play_movie_button">चलचित्र प्ले गर्नुहोस्</string>
<string name="play_trailer_button">ट्रेलर प्ले गर्नुहोस्</string>
<string name="play_livestream_button">लाइभस्ट्रिम प्ले गर्नुहोस्</string>
<string name="pick_subtitle">उपशीर्षक</string>
<string name="reload_error">पुनः प्रयास गर्नुहोस…</string>
<string name="go_back">पछाडि जानुहोस्</string>
<string name="play_episode">एपिसोड प्ले गर्नुहोस्</string>
<string name="download">डाउनलोड</string>
<string name="downloaded">डाउनलोड भयो</string>
<string name="downloading">डाउनलोड हुदैछ</string>
<string name="download_paused">डाउनलोड रोकियो</string>
<string name="download_started">डाउनलोड सुरु भयो</string>
<string name="download_failed">डाउनलोड असफल भयो</string>
<string name="download_canceled">डाउनलोड रद्द गरियो</string>
<string name="download_done">डाउनलोड भयो</string>
<string name="update_started">अपडेट सुरु</string>
<string name="stream">स्ट्रिम</string>
<string name="error_loading_links_toast">लिङ्क लोड गर्दा त्रुटि भयो</string>
<string name="links_reloaded_toast">लिङ्कहरू रिलोड गरियो</string>
<string name="download_storage_text">भित्री स्टोरेज</string>
<string name="app_dubbed_text">Dub</string>
<string name="app_subbed_text">Sub</string>
<string name="popup_delete_file">फाइल मेट्नुहोस्</string>
<string name="popup_play_file">फाइल प्ले गर्नुहोस्</string>
<string name="popup_resume_download">डाउनलोड सुचारु गर्नुहोस्</string>
<string name="popup_pause_download">डाउनलोड रोक्नुहोस्</string>
<string name="home_more_info">थप जानकारी</string>
<string name="home_expanded_hide">लुकाउनुहोस्</string>
<string name="home_play">प्ले</string>
<string name="home_info">जानकारी</string>
<string name="filter_bookmarks">बुकमार्कहरू फिल्टर गर्नुहोस्</string>
<string name="error_bookmarks_text">बुकमार्कहरू</string>
<string name="action_remove_from_bookmarks">हटाउनुहोस्</string>
<string name="action_add_to_bookmarks">हेरेको स्थिति राख्नुहोस्</string>
<string name="sort_copy">कपी</string>
<string name="sort_close">बन्द</string>
<string name="sort_clear">खाली गर्नुहोस्</string>
<string name="sort_save">सेव</string>
<string name="next_episode_format" formatted="true">एपिसोड %d रिलीज हुने समय</string>
<string name="no_data">डाटा छैन</string>
<string name="next_episode">अर्को एपिसोड</string>
<string name="type_watching">हेर्दै गरेको</string>
<string name="type_completed">पुरा भएको</string>
<string name="type_re_watching">पुन: हेर्दै</string>
<string name="play_torrent_button">स्ट्रिम टोरेन्ट</string>
<string name="pick_source">स्रोतहरू</string>
<string name="pref_disable_acra">स्वचालित बग रिपोर्टिङ असक्षम गर्नुहोस्</string>
<string name="sort_apply">लागू गर्नुहोस्</string>
</resources>

View File

@ -532,4 +532,11 @@
\nLaster ikke inn noen utvidelser ved oppstart til filen er fjernet.</string>
<string name="empty_library_no_accounts_message">Biblioteket ditt er tomt :(
\nLogg inn på en bibliotekkonto eller legg til programmer i ditt lokale bibliotek.</string>
<string name="edit">Rediger</string>
<string name="profiles">Profiler</string>
<string name="favorites_list_name">Favoritter</string>
<string name="lock_profile">Lås profil</string>
<string name="use">Bruk</string>
<string name="help">Hjelp</string>
<string name="profile_background_des">Profilbakgrunn</string>
</resources>

View File

@ -158,4 +158,5 @@
<string name="next_episode">ପରବର୍ତ୍ତୀ ଅଧ୍ୟାୟ</string>
<string name="no_data">କୌଣସି ତଥ୍ୟ ନାହିଁ</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s ଅ %2$d</string>
<string name="video_skip_op">ଆଦ୍ୟ ବାଦ୍ ଦିଅ</string>
</resources>

View File

@ -554,4 +554,42 @@
<string name="no_plugins_found_error">Nie znaleziono żadnych wtyczek w repozytorium</string>
<string name="already_voted">Już oddano głos</string>
<string name="no_repository_found_error">Nie znaleziono tego repozytorium, sprawdź adres URL lub spróbuj połączyć się przez VPN</string>
<string name="favorite_removed">Usunięto %s z ulubionych</string>
<string name="favorites_list_name">Ulubione</string>
<string name="favorite_added">Dodano %s do ulubionych</string>
<string name="duplicate_message_multiple" formatted="true">W swojej bibliotece znaleziono potencjalne duplikaty:
\n
\n%s
\n
\nCzy chcesz dodać ten element, zastąpić istniejące, czy anulować operację?</string>
<string name="enter_pin_with_name" formatted="true">Wprowadź pin dla %s</string>
<string name="backup_frequency">Częstotliwość tworzenia kopii zapasowych</string>
<string name="duplicate_title">Znaleziono potencjalny duplikat</string>
<string name="lock_profile">Zablokuj profil</string>
<string name="action_add_to_favorites">Dodaj do ulubionych</string>
<string name="manage_accounts">Zarządzaj kontami</string>
<string name="duplicate_replace_all">Zamień wszystko</string>
<string name="edit_account">Edytuj konto</string>
<string name="pin_error_incorrect">Nieprawidłowy PIN. Spróbuj ponownie.</string>
<string name="action_unsubscribe">Anuluj subskrypcję</string>
<string name="pin_error_length">Kod PIN musi mieć 4 znaki</string>
<string name="duplicate_replace">Zastąp</string>
<string name="duplicate_add">Dodaj</string>
<string name="action_subscribe">Zasubskrybuj</string>
<string name="action_remove_from_favorites">Usuń z ulubionych</string>
<string name="select_an_account">Wybierz konto</string>
<string name="duplicate_message_single" formatted="true">Wygląda się, że potencjalny duplikat już znajduje się w bibliotece: \'%s\'. \'
\n
\nCzy chciałbyś dodać ten element, zastąpić istniejący, czy anulować akcję?</string>
<string name="enter_pin">Wprowadź PIN</string>
<string name="pin">PIN</string>
<string name="links_reloaded_toast">Linki załadowane ponownie</string>
<string name="enter_current_pin">Wprowadź obecny PIN</string>
<string name="logged_account" formatted="true">Zalogowano jako %s</string>
<string name="rotate_video_desc">Wyświetl przycisk przełączający orientację ekranu</string>
<string name="use_default_account">Używaj domyślnego konta</string>
<string name="skip_startup_account_select_pref">Pomiń wybór konta podczas uruchamiania</string>
<string name="auto_rotate_video">Automatyczny obrót</string>
<string name="rotate_video">Obrót</string>
<string name="auto_rotate_video_desc">Włącz automatyczne przełączanie orientacji ekranu na podstawie orientacji filmu</string>
</resources>

View File

@ -421,12 +421,12 @@
<string name="pref_category_looks">Oтoбpaжeниe</string>
<string name="trailer">Трейлер</string>
<string name="single_plugin_disabled" formatted="true">%s (отключено)</string>
<string name="next">Следующий</string>
<string name="next">Далее</string>
<string name="blank_repo_message">В CloudStream по умолчанию не установлены сайты. Вам необходимо установить сайты из репозиториев.
\n
\nИз-за безмозглой DMCA-атаки со стороны Sky UK Limited 🤮 мы не можем привязать сайт репозитория в приложении.
\nИз-за безмозглой жалобы DMCA от Sky UK Limited 🤮 мы не можем привязать сайт репозитория в приложении.
\n
\nПрисоединяйтесь к нашему Discord или ищите в интернете.</string>
\nПрисоединяйтесь к нашему Discord-серверу или найдите в интернете.</string>
<string name="error_invalid_data">Недопустимые данные</string>
<string name="resolution_and_title">Разрешение и название</string>
<string name="previous">Предыдущий</string>
@ -551,4 +551,5 @@
\nБудет иметь общий приоритет видео 10.
\n
\nПРИМЕЧАНИЕ. Если сумма равна 10 или более, плеер автоматически пропустит загрузку при загрузке этой ссылки!</string>
<string name="links_reloaded_toast">Ссылки перезагружены</string>
</resources>

View File

@ -13,7 +13,7 @@
<string name="search_poster_img_des">Affisch</string>
<string name="no_data">Ingen Data</string>
<string name="episode_more_options_des">Mer Instälningar</string>
<string name="go_back_img_des">Gå tillbacka</string>
<string name="go_back_img_des">Gå tillbaka</string>
<string name="next_episode">Nästa avsnitt</string>
<string name="result_poster_img_des">Affisch</string>
<string name="result_tags">Genrer</string>
@ -24,8 +24,8 @@
<string name="type_watching">Tittar på</string>
<string name="type_on_hold">Pausad</string>
<string name="type_completed">Avslutad</string>
<string name="type_dropped">Dropped</string>
<string name="type_plan_to_watch">Plannerad</string>
<string name="type_dropped">Släppt</string>
<string name="type_plan_to_watch">Planerad</string>
<string name="play_movie_button">Spela Upp</string>
<string name="play_torrent_button">Strömma Torrent</string>
<string name="pick_source">Källor</string>
@ -39,10 +39,10 @@
<string name="app_dubbed_text">Dub</string>
<string name="app_subbed_text">Sub</string>
<string name="popup_delete_file">Ta bort</string>
<string name="popup_play_file">Spela upp</string>
<string name="popup_play_file">Spela upp fil</string>
<string name="pref_disable_acra">Inaktivera automatisk felrapportering</string>
<string name="home_more_info">Mer information</string>
<string name="home_expanded_hide">Hide</string>
<string name="home_expanded_hide">Göm</string>
<string name="home_main_poster_img_des">@string/result_poster_img_des</string>
<string name="home_play">Spela upp</string>
<string name="home_info">Info</string>
@ -64,10 +64,10 @@
<string name="subs_font">Font</string>
<string name="search_provider_text_providers">Sök med följande leverantörer</string>
<string name="search_provider_text_types">Sök med följande filmtyper</string>
<string name="benene_count_text">%d Bananer donerade till utvecklarna</string>
<string name="benene_count_text">%d bananer donerade till utvecklarna</string>
<string name="benene_count_text_none">Inga bananer givna</string>
<string name="subs_auto_select_language">Automatisk val av undertextspråk</string>
<string name="subs_download_languages">Automatisk nerladdaning av språk</string>
<string name="subs_download_languages">Automatisk nedladdning av språk</string>
<string name="subs_hold_to_reset_to_default">Håll inne för att återställa till standard</string>
<string name="continue_watching">Fortsätt titta</string>
<string name="action_remove_watching">Ta bort</string>
@ -297,7 +297,7 @@
<string name="referer">Referer</string>
<string name="next">Nästa</string>
<string name="subtitle_offset_hint">1000 ms</string>
<string name="add_site_summary">Lägga till en klon av en befintlig webbplats med en annan webbadress.</string>
<string name="add_site_summary">Lägga till en klon av en befintlig webbplats med en annan webbadress</string>
<string name="error_invalid_id">Ogiltigt ID</string>
<string name="error_invalid_url">Ogiltig webbadress</string>
<string name="video_ram_description">Orsakar krascher om inställningen är för hög på enheter med lågt minne, t.ex. Android TV.</string>
@ -403,7 +403,7 @@
<string name="restart">Starta om</string>
<string name="category_provider_test">Testa leverantörer</string>
<string name="watch_quality_pref_data">Föredragen videokvalitet (Mobildata)</string>
<string name="random_button_settings_desc">Visa en slumpknapp på förstasidan</string>
<string name="random_button_settings_desc">Visa slumpmässig knapp på förstasidan och biblioteket</string>
<string name="is_adult">18+</string>
<string name="audio_tracks">Ljudspår</string>
<string name="livestreams">Direktsändningar</string>
@ -432,4 +432,164 @@
<string name="profiles">Profiler</string>
<string name="help">Hjälp</string>
<string name="qualities">Kvalitet</string>
<string name="stream">Ström</string>
<string name="repository_name_hint">Databasens namn</string>
<string name="batch_download_nothing_to_download_format" formatted="true">All %s har redan laddats ner</string>
<string name="download_all_plugins_from_repo">Ladda ner alla tillägg från den här databasen?</string>
<string name="safe_mode_title">Felsäkert läge på</string>
<string name="apply_on_restart">Applicera vid omstart</string>
<string name="player_settings_play_in_app">Intern spelare</string>
<string name="quality_cam_hd">Kamera HD</string>
<string name="plugin_downloaded">Tillägg nedladdad</string>
<string name="repository_url_hint">Lager URL</string>
<string name="batch_download_start_format" formatted="true">Börjat hämta %1$d %2$s…</string>
<string name="bottom_title_settings">Affisch titel plats</string>
<string name="player_loaded_subtitles" formatted="true">Laddat %s</string>
<string name="actor_main">Huvudsaklig</string>
<string name="actor_supporting">Stödjande</string>
<string name="actor_background">Bakgrund</string>
<string name="view_public_repositories_button_short">Offentlig lista</string>
<string name="extension_language">Språk</string>
<string name="extension_install_first">Installera tillägget först</string>
<string name="player_pref">Önskad videospelare</string>
<string name="android_tv_interface_on_seek_settings">Spelare visas - Sök förlopp</string>
<string name="android_tv_interface_on_seek_settings_summary">Sök förlopp som används när spelaren är synlig</string>
<string name="start">Starta</string>
<string name="stop">Stopp</string>
<string name="revert">Återställ</string>
<string name="subscription_deleted">Avslutat prenumerationen på %s</string>
<string name="subscription_episode_released">Avsnitt %d släppt!</string>
<string name="empty_library_logged_in_message">Den här listan är tom. Försök byta till en annan.</string>
<string name="plugin_deleted">Tillägg borttagen</string>
<string name="plugin_loaded">Tillägg laddade</string>
<string name="plugin_singular">Tillägg</string>
<string name="backup_frequency">Säkerhetskopierings antal</string>
<string name="episode_sync_settings">Uppdatera visnings förlopp</string>
<string name="automatic_plugin_download_mode_title">Välj läge för att filtrera nedladdning av plugins</string>
<string name="extension_version">Utgåva</string>
<string name="extension_status">Status</string>
<string name="enter_pin">Ange PIN-kod</string>
<string name="enter_pin_with_name" formatted="true">Ange PIN-kod för %s</string>
<string name="enter_current_pin">Ange aktuell PIN-kod</string>
<string name="lock_profile">Lås profil</string>
<string name="pin_error_incorrect">Felaktig PIN-kod. Försök igen.</string>
<string name="pin_error_length">PIN-koden måste vara fyra tecken</string>
<string name="select_an_account">Välj ett konto</string>
<string name="manage_accounts">Hantera konton</string>
<string name="edit_account">Redigera konto</string>
<string name="logged_account" formatted="true">Loggat in som %s</string>
<string name="skip_startup_account_select_pref">Hoppa över val av konto vid start</string>
<string name="auto_rotate_video_key">auto_rotera_video_nyckel</string>
<string name="jsdelivr_proxy_summary">Förbi passera blockering av GitHub genom att använda jsDelivr. Kan göra att uppdateringar försenas med några dagar.</string>
<string name="pref_category_actions">Funktion</string>
<string name="preferred_media_settings">Önskad media</string>
<string name="bottom_title_settings_des">Lägg titeln under affischen</string>
<string name="added_sync_format" formatted="true">Tillagt %s</string>
<string name="add_sync">Lägg till spårning</string>
<string name="sync_score">Betygsatt</string>
<string name="disable">Inaktivera</string>
<string name="quality_webrip">Web</string>
<string name="poster_image">Affischbild</string>
<string name="preferred_media_subtext">Vad vill du se</string>
<string name="add_repository">Lägg till databas</string>
<string name="plugins_updated" formatted="true">Uppdaterade %d tillägg</string>
<string name="batch_download_finish_format" formatted="true">Nedladdat %1$d %2$s</string>
<string name="plugins_disabled" formatted="true">Inaktiverad: %d</string>
<string name="plugins_not_downloaded" formatted="true">Ej hämtad: %d</string>
<string name="view_public_repositories_button">Visa community databaser</string>
<string name="skip_type_mixed_op">Blandad inledning</string>
<string name="skip_type_format" formatted="true">Skippa %s</string>
<string name="blank_repo_message">CloudStream har inga webbplatser installerade som standard. Du måste installera webbplatserna från databaser.
\n
\nPå grund av en hjärnlös DMCA-borttagning av Sky UK Limited kan vi inte länka databaser på denna applikation.
\n
\nGå med i vår Discord eller sök online.</string>
<string name="select_library">Välj bibliotek</string>
<string name="empty_library_no_accounts_message">Ditt bibliotek är tomt :(
\nLogga in på ett bibliotekskonto eller lägg till program i ditt lokala bibliotek.</string>
<string name="enable_skip_op_from_database_des">Visa hoppa över popups för introduktion/eftertexter</string>
<string name="action_remove_from_watched">Ta bort från sett</string>
<string name="safe_mode_file">Fil i säkertläge hittades!
\nLaddar inte några tillägg vid start tills filen har tagits bort.</string>
<string name="subscription_in_progress_notification">Uppdaterar prenumererade program</string>
<string name="subscription_list_name">Prenumererad</string>
<string name="action_subscribe">Prenumerera</string>
<string name="unable_to_inflate">UI kunde inte skapas korrekt, detta är en MASSIV BUG och bör rapporteras omedelbart %s</string>
<string name="already_voted">Du har redan röstat</string>
<string name="action_unsubscribe">Avprenumerera</string>
<string name="action_add_to_favorites">Lägg till i favoriter</string>
<string name="duplicate_add">Lägg till</string>
<string name="duplicate_title">Potentiell Dublett Hittad</string>
<string name="action_remove_from_favorites">Ta bort från favoriter</string>
<string name="duplicate_replace">Ersätt</string>
<string name="duplicate_replace_all">Ersätt alla</string>
<string name="filler" formatted="true">Filler</string>
<string name="pref_category_bypass">Förbikoppla ISP</string>
<string name="quality_cam_rip">Kamera</string>
<string name="quality_cam">Kamera</string>
<string name="safe_mode_description">Alla tillägg stängdes av på grund av en krasch för att hjälpa dig hitta den som orsakar problem.</string>
<string name="extension_size">Storlek</string>
<string name="extension_authors">Författarna</string>
<string name="extension_types">Stödd</string>
<string name="no_plugins_found_error">Inga tillägg hittades i databasen</string>
<string name="no_repository_found_error">Databasen hittades inte, kontrollera URL:n och prova VPN</string>
<string name="batch_download">Flerfils nedladdning</string>
<string name="plugin">Tillägg</string>
<string name="plugins_downloaded" formatted="true">Hämtat: %d</string>
<string name="extension_rating" formatted="true">Betyg: %s</string>
<string name="clipboard_too_large">För mycket text. Det gick inte att spara till urklipp.</string>
<string name="quality_hq">Hög kvalite</string>
<string name="upload_sync">Synka</string>
<string name="skip_type_ed">Slutet</string>
<string name="skip_type_mixed_ed">Blandad avslut</string>
<string name="subscription_new">Prenumererar på %s</string>
<string name="safe_mode_crash_info">Visa krasch info</string>
<string name="hls_playlist">HLS spellista</string>
<string name="plugin_load_fail" formatted="true">Kunde inte ladda %s</string>
<string name="skip_type_op">Inledning</string>
<string name="skip_type_recap">Sammanfattning</string>
<string name="pref_category_gestures">Gester</string>
<string name="authenticated_user" formatted="true">%s autentiserad</string>
<string name="extras">Statister</string>
<string name="network_adress_example">Länka till strömmen</string>
<string name="delete_repository">Radera databasen</string>
<string name="profile_background_des">Profil bakgrund</string>
<string name="quality_workprint">WP</string>
<string name="auto_rotate_video">Auto rotera</string>
<string name="rotate_video">Rotera</string>
<string name="rotate_video_desc">Visa en växlingsknapp för skärmorientering</string>
<string name="auto_rotate_video_desc">Aktivera automatisk växling av skärmorientering baserat på videoorientering</string>
<string name="links_reloaded_toast">Länkar omladdade</string>
<string name="android_tv_interface_off_seek_settings">Spelare dold - Sökförlopp</string>
<string name="quality_blueray">Blu-ray</string>
<string name="delete_repository_plugins">Detta kommer också att ta bort alla tillägg för databasen</string>
<string name="setup_extensions_subtext">Ladda ner listan över webbplatser du vill använda</string>
<string name="single_plugin_disabled" formatted="true">%s (Inaktiverad)</string>
<string name="extension_description">Beskrivning</string>
<string name="skip_type_creddits">Eftertexter</string>
<string name="skip_type_intro">Introduktion</string>
<string name="favorites_list_name">Favoriter</string>
<string name="set_default">Ange standard</string>
<string name="favorite_removed">%s togs bort från favoriter</string>
<string name="favorite_added">%s har lagts till i favoriter</string>
<string name="use_default_account">Använd standard konto</string>
<string name="rotate_video_key">rotera_video_nyckel</string>
<string name="pin">PIN-kod</string>
<string name="android_tv_interface_off_seek_settings_summary">Sök mängden som används när spelaren är dold</string>
<string name="duplicate_message_multiple" formatted="true">Det verkar som om ett potentiellt duplicerat objekt redan finns i ditt bibliotek:
\n
\n\'%s.\'
\n
\nVill du lägga till det här objektet ändå, ersätta det befintliga eller avbryta åtgärden?</string>
<string name="duplicate_message_single" formatted="true">Det verkar som om ett potentiellt duplicerat objekt redan finns i ditt bibliotek: \'%s.\'
\n
\nVill du lägga till det här objektet ändå, ersätta det befintliga eller avbryta åtgärden?</string>
<string name="quality_profile_help">Här kan du ändra hur källorna ska sorteras, om en video har högre prioritet visas den högre upp i källvalet. Summan av källprioriteten och kvalitetsprioriteten är videoprioriteten.
\n
\nKälla A: 3
\nKvalitet B: 7
\nKommer att ha en kombinerad videoprioritet på 10.
\n
\nOBS: Om summan är 10 eller mer kommer spelaren automatiskt att hoppa över laddningen när den länken laddas!</string>
<string name="copyTitle">Titel kopierad!</string>
</resources>

View File

@ -262,4 +262,8 @@
<string name="add_account">Magdagdag ng Account</string>
<string name="history">Kasaysayan</string>
<string name="action_mark_as_watched">I-tanda bilang napanood na</string>
<string name="update_started">Nagsimula ang Update</string>
<string name="chromecast_subtitles_settings">Mga Subtitle ng Chromecast</string>
<string name="chromecast_subtitles_settings_des">Mga setting ng mga subtitle ng Chromecast</string>
<string name="play_trailer_button">Maglaro ng Trailer</string>
</resources>

View File

@ -308,7 +308,7 @@
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
<string name="category_general">Genel</string>
<string name="random_button_settings">Rastgele İçerik</string>
<string name="random_button_settings_desc">Ana sayfada rastgele bir film veya dizi seçen bir tuş göster</string>
<string name="random_button_settings_desc">Anasayfada ve Kütüphanede rastgele düğmesini göster</string>
<string name="provider_lang_settings">Sağlayıcı dilleri</string>
<string name="app_layout">Uygulama düzeni</string>
<string name="preferred_media_settings">Tercih edilen medya</string>
@ -638,4 +638,5 @@
<string name="rotate_video">Döndür</string>
<string name="auto_rotate_video_desc">Video yönüne göre ekran yönünün otomatik olarak değişmesini sağla</string>
<string name="links_reloaded_toast">Bağlantılar Yeniden Yüklendi</string>
<string name="copyTitle">Başlık kopyalandı!</string>
</resources>

View File

@ -282,7 +282,7 @@
<string name="pref_category_ui_features">Особливості</string>
<string name="category_general">Загальне</string>
<string name="random_button_settings">Випадкова кнопка</string>
<string name="random_button_settings_desc">Показувати кнопку на Головній сторінці, яка вибирає випадковий фільм чи серіал</string>
<string name="random_button_settings_desc">Показувати кнопку на Головній сторінці та в Бібліотеці, яка вибирає випадковий фільм чи серіал</string>
<string name="provider_lang_settings">Мови постачальника</string>
<string name="app_layout">Макет застосунку</string>
<string name="preferred_media_settings">Бажані медіа</string>
@ -591,4 +591,5 @@
<string name="auto_rotate_video_key">auto_rotate_video_key</string>
<string name="auto_rotate_video">Автоповорот</string>
<string name="auto_rotate_video_desc">Ввімкнути автоматичний поворот екрана на основі орієнтації відео</string>
<string name="copyTitle">Назву скопійовано!</string>
</resources>

View File

@ -309,7 +309,7 @@
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
<string name="category_general">通用</string>
<string name="random_button_settings">随机按钮</string>
<string name="random_button_settings_desc">在主页中显示随机按钮</string>
<string name="random_button_settings_desc">在主页和库中显示随机按钮</string>
<string name="provider_lang_settings">片源语言</string>
<string name="app_layout">应用布局</string>
<string name="preferred_media_settings">首选类型</string>
@ -592,4 +592,22 @@
\n注意如果总和为 10 或更多,则加载该链接时播放器将自动跳过加载!</string>
<string name="qualities">质量</string>
<string name="profile_background_des">个人资料背景</string>
<string name="pin">PIN</string>
<string name="links_reloaded_toast">链接已重新加载</string>
<string name="disable">禁用</string>
<string name="pin_error_incorrect">PIN码不正确请再试一次。</string>
<string name="pin_error_length">PIN码必须为4个字符</string>
<string name="select_an_account">选择一个账户</string>
<string name="manage_accounts">账户管理</string>
<string name="action_subscribe">订阅</string>
<string name="action_unsubscribe">取消订阅</string>
<string name="duplicate_add">添加</string>
<string name="duplicate_replace">替换</string>
<string name="duplicate_replace_all">替换全部</string>
<string name="no_plugins_found_error">仓库中未找到插件</string>
<string name="rotate_video">旋转</string>
<string name="auto_rotate_video">自动旋转</string>
<string name="logged_account" formatted="true">以%s的身份登录</string>
<string name="use_default_account">使用默认账户</string>
<string name="automatic_plugin_download_mode_title">选择过滤插件的下载模式</string>
</resources>

View File

@ -67,6 +67,8 @@
<string name="enable_nsfw_on_providers_key" translatable="false">enable_nsfw_on_providers_key</string>
<string name="skip_startup_account_select_key" translatable="false">skip_startup_account_select_key</string>
<string name="enable_skip_op_from_database" translatable="false">enable_skip_op_from_database</string>
<string name="rotate_video_key" translatable="false">rotate_video_key</string>
<string name="auto_rotate_video_key" translatable="false">auto_rotate_video_key</string>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
<string name="storage_size_format" formatted="true" translatable="false">%s • %s</string>
@ -146,7 +148,7 @@
<string name="download_done">Download Done</string>
<string name="download_format" translatable="false">%s - %s</string>
<string name="update_started">Update Started</string>
<string name="stream">Stream</string>
<string name="stream">Network stream</string>
<string name="error_loading_links_toast">Error Loading Links</string>
<string name="links_reloaded_toast">Links Reloaded</string>
<string name="download_storage_text">Internal Storage</string>
@ -171,6 +173,11 @@
<string name="sort_close">Close</string>
<string name="sort_clear">Clear</string>
<string name="sort_save">Save</string>
<string name="copyTitle">Title copied!</string>
<string name="copyRepoUrl">Repo URL copied!</string>
<string name="subscribe_tooltip">New episode notification</string>
<string name="result_search_tooltip">Search in other extensions</string>
<string name="recommendations_tooltip">Show recommendations</string>
<string name="player_speed">Player Speed</string>
<string name="subtitles_settings">Subtitle Settings</string>
<string name="subs_text_color">Text Color</string>
@ -210,8 +217,8 @@
<string name="player_subtitles_settings_des">Player subtitles settings</string>
<string name="chromecast_subtitles_settings">Chromecast Subtitles</string>
<string name="chromecast_subtitles_settings_des">Chromecast subtitles settings</string>
<string name="eigengraumode_settings">Eigengravy Mode</string>
<string name="eigengraumode_settings_des">Adds a speed option in the player</string>
<string name="eigengraumode_settings">Playback speed</string>
<string name="speed_setting_summary">Adds a speed option in the player</string>
<string name="swipe_to_seek_settings">Swipe to seek</string>
<string name="swipe_to_seek_settings_des">Swipe from side to side to control your position in a video</string>
<string name="swipe_to_change_settings">Swipe to change settings</string>
@ -388,9 +395,9 @@
<string name="video_disk_description">Causes problems if set too high on devices with low storage space, such as Android TV.</string>
<string name="dns_pref">DNS over HTTPS</string>
<string name="dns_pref_summary">Useful for bypassing ISP blocks</string>
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string>
<string name="jsdelivr_proxy">GitHub Proxy</string>
<string name="jsdelivr_enabled">Could not reach GitHub. Turning on jsDelivr proxy…</string>
<string name="jsdelivr_proxy_summary">Bypasses blocking of GitHub using jsDelivr. May cause updates to be delayed by few days.</string>
<string name="jsdelivr_proxy_summary">Bypass blocking of raw github URLs using jsDelivr. May cause updates to be delayed by few days.</string>
<string name="add_site_pref">Clone site</string>
<string name="remove_site_pref">Remove site</string>
<string name="add_site_summary">Add a clone of an existing site, with a different URL</string>
@ -435,14 +442,16 @@
<string name="pref_category_ui_features">Features</string>
<string name="category_general">General</string>
<string name="random_button_settings">Random Button</string>
<string name="random_button_settings_desc">Show random button on Homepage</string>
<string name="provider_lang_settings">Provider languages</string>
<string name="random_button_settings_desc">Show random button on Homepage and Library</string>
<string name="provider_lang_settings">Extension languages</string>
<string name="app_layout">App Layout</string>
<string name="preferred_media_settings">Preferred media</string>
<string name="enable_nsfw_on_providers">Enable NSFW on supported providers</string>
<string name="enable_nsfw_on_providers">Enable NSFW on supported Extensions</string>
<string name="subtitles_encoding">Subtitle encoding</string>
<string name="category_providers">Providers</string>
<string name="category_provider_test">Provider test</string>
<string name="test_extensions">Test all Extensions</string>
<string name="test_extensions_summary">This Test is meant for developers only and does not verifies or denies working of any extension.</string>
<string name="category_ui">Layout</string>
<string name="automatic">Auto</string>
<string name="tv_layout">TV layout</string>
@ -459,11 +468,11 @@
<string name="opensubtitles_key" translatable="false">opensubtitles_key</string>
<string name="nginx_key" translatable="false">nginx_key</string>
<string name="example_password">password123</string>
<string name="example_username">MyCoolUsername</string>
<string name="example_username">Username</string>
<string name="example_email">hello@world.com</string>
<string name="example_ip">127.0.0.1</string>
<string name="example_site_name">MyCoolSite</string>
<string name="example_site_url">example.com</string>
<string name="example_site_name">NewSiteName</string>
<string name="example_site_url">https://example.com</string>
<string name="example_lang_name">Language code (en)</string>
<!--
<string name="mal_account_settings" translatable="false">MAL</string>
@ -555,8 +564,8 @@
<string name="subtitles_filter_lang">Filter by preferred media language</string>
<string name="extras">Extras</string>
<string name="trailer">Trailer</string>
<string name="network_adress_example">Link to stream</string>
<string name="referer">Referer</string>
<string name="network_adress_example">https://example.com/example.mp4</string>
<string name="referer">Referer (optional)</string>
<string name="next">Next</string>
<string name="provider_languages_tip">Watch videos in these languages</string>
<string name="previous">Previous</string>
@ -591,8 +600,6 @@
<string name="plugins_updated" formatted="true">Updated %d plugins</string>
<string name="blank_repo_message">CloudStream has no sites installed by default. You need to install the sites from repositories.
\n
\nBecause of a brainless DMCA takedown by Sky UK Limited 🤮 we cannot link the repository site in app.
\n
\nJoin our Discord or search online.</string>
<string name="view_public_repositories_button">View community repositories</string>
<string name="view_public_repositories_button_short">Public list</string>
@ -735,9 +742,7 @@
<string name="skip_startup_account_select_pref">Skip account selection at startup</string>
<string name="use_default_account">Use Default Account</string>
<string name="rotate_video">Rotate</string>
<string name="rotate_video_key">rotate_video_key</string>
<string name="rotate_video_desc">Display a toggle button for screen orientation</string>
<string name="auto_rotate_video_key">auto_rotate_video_key</string>
<string name="auto_rotate_video_desc">Enable automatic switching of screen orientation based on video orientation</string>
<string name="auto_rotate_video">Auto rotate</string>
</resources>

View File

@ -70,7 +70,7 @@
app:key="@string/player_resize_enabled_key" />
<SwitchPreference
android:icon="@drawable/ic_baseline_speed_24"
android:summary="@string/eigengraumode_settings_des"
android:summary="@string/speed_setting_summary"
android:title="@string/eigengraumode_settings"
app:defaultValue="false"
app:key="@string/playback_speed_enabled_key" />

View File

@ -25,6 +25,7 @@
<Preference
android:icon="@drawable/baseline_network_ping_24"
android:key="@string/test_providers_key"
android:title="Test all providers" />
android:title="@string/test_extensions"
android:summary="@string/test_extensions_summary"/>
</PreferenceScreen>

View File

@ -5,8 +5,8 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.1.4")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21")
classpath("com.android.tools.build:gradle:8.2.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
// NOTE: Do not place your application dependencies here; they belong
@ -22,7 +22,7 @@ allprojects {
}
plugins {
id("com.google.devtools.ksp") version "1.9.21-1.0.15" apply false
id("com.google.devtools.ksp") version "1.9.22-1.0.16" apply false
}
tasks.register<Delete>("clean") {

View File

@ -0,0 +1 @@
عملو ستريم للأفلاف والمسلسلات والأنمي.

View File

@ -0,0 +1 @@
- Dodan je dnevnik promjena!

View File

@ -0,0 +1,10 @@
CloudStream-3 omogućuje emitiranje i preuzimanje filmova, TV serija i animea.
Aplikacija ne koristi oglase i analitike te
podržava stranice s trailerima, filmovima i više, npr.
Oznake
Preuzimanja titlova
Chromecast podrška

View File

@ -0,0 +1 @@
Emitirajte i preuzmite filmove, TV serije i anime.

View File

@ -0,0 +1 @@
CloudStream

View File

@ -0,0 +1 @@
- Ändringslogg tillagd!

View File

@ -0,0 +1,10 @@
CloudStream-3 låter dig streama och ladda ner filmer, TV-serier och anime.
Appen levereras utan annonser och analyser och
stöder flera trailer- och film webbplatser och mer, t.ex.
Bokmärken
Nedladdning av undertexter
Stöd för Chromecast

View File

@ -0,0 +1 @@
Streama och ladda ner filmer, TV serier och anime.

View File

@ -0,0 +1 @@
CloudStream

View File

@ -19,6 +19,5 @@ android.useAndroidX=true
# android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@ -1,6 +1,6 @@
#Fri Apr 30 17:11:15 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME