Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit dd8f414

Browse files
committed
Update to 0.0.3 --
Changelog: - Change extract method. - Add getVideoMeta() method.
1 parent ac8967a commit dd8f414

File tree

6 files changed

+146
-41
lines changed

6 files changed

+146
-41
lines changed

.idea/markdown-navigator.xml

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ A lightweight Android (Kotlin) library for extract YouTube streaming URL. Port f
99
## Features
1010

1111
- Get YouTube stream Url with all format and itag
12+
- Get YouTube video metadata
1213
- Using Kotlin Coroutines for best performance
1314
## Using in your project
1415
### Gradle
@@ -35,43 +36,82 @@ Then, add dependencies in app level build.gradle:
3536

3637
```kotlin
3738
dependencies {
38-
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:0.0.2'
39+
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:0.0.3'
3940
}
4041
```
4142

4243
## How to use
4344

4445
### Using with Kotlin Coroutines
46+
>From version 0.0.3, this library change extract method and add more feature method.
4547
4648
Before start please add Kotlin Coroutines to your project
4749

4850
Call YTExtractor inside your activity or fragment
4951
```kotlin
5052
//If your YouTube link is "https://www.youtube.com/watch?v=IDwytT0wFRM" so this videoId is "IDwytT0wFRM"
5153
var videoId = "IDwytT0wFRM"
54+
val yt = YTExtractor(context)
55+
var ytFiles: SparseArray<YtFile>? = null
56+
var videoMeta: VideoMeta? = null
5257
GlobalScope.launch {
53-
val ytFiles = YTExtractor(context).getYtFile(videoId)
54-
var streamUrl = ytFiles[251].url
58+
yt.extract(videoId)
59+
//Before get YtFile or VideoMeta, you need to check state of yt object
60+
if (yt.state == State.SUCCESS) {
61+
ytFiles = yt.getYtFile()
62+
videoMeta = yt.getVideoMeta()
63+
}
5564
}
5665
```
57-
In above case, ytFiles is a map of available media files for one YouTube video, accessible by their itag value (in above code "251" is a audio itag).
66+
In above case, ytFiles is a map of available media files for one YouTube video, accessible by their itag value (in above code "251" is a audio itag) and videoMeta is an object that contains all metadata of this YouTube video (thumbnail, title, author, views, etc.).
5867

68+
#### YtFile
69+
After use getYtFile() function, you will get SparseArray<YtFile> object. This object contains all available media files for one YouTube video, accessible by their itag value. You can get YtFile object by itag value like this:
70+
```kotlin
71+
var ytFiles = yt.getYtFile()
72+
var ytFile = ytFiles.get(251) // 251 is itag of audio
73+
//Get stream URL
74+
var streamUrl = ytFile?.url
75+
```
76+
#### VideoMeta
77+
After use getVideoMeta() function, you will get VideoMeta object. This object contains all metadata of this YouTube video (thumbnail, title, author, views, etc.). You can get all metadata like this:
78+
```kotlin
79+
var videoMeta = yt.getVideoMeta()
80+
//title
81+
var title = videoMeta?.title
82+
//author
83+
var author = videoMeta?.author
84+
//view count
85+
var views = videoMeta?.viewCount
86+
//video length in second
87+
var videoLength = videoMeta?.videoLength
88+
//video channel id
89+
var videoChannelId = videoMeta?.channelId
90+
91+
//Get thumbnail
92+
// Default resolution
93+
val thumbUrl = videoMeta?.thumbUrl
94+
// 320 x 180
95+
val mqImageUrl = videoMeta?.mqImageUrl
96+
// 480 x 360
97+
val hqImageUrl = videoMeta?.hqImageUrl
98+
// 640 x 480
99+
val sdImageUrl = videoMeta?.sdImageUrl
100+
// Max Res
101+
val maxResImageUrl = videoMeta?.maxResImageUrl
102+
```
59103
#### Filter
60104
To get list of only video YtFile or only audio, you can call this function
61105
```kotlin
62-
GlobalScope.launch {
63-
val ytFiles = YTExtractor(context).getYtFile(videoId)
64-
val videoYtFiles = ytFiles.getAudioOnly() // Return ArrayList<YtFile> of only video
65-
val audioYtFiles = ytFiles.getVideoOnly() // Return ArrayList<YtFile> of only audio
66-
}
106+
val ytFiles = yt.getYtFile()
107+
val videoYtFiles = ytFiles.getAudioOnly() // Return ArrayList<YtFile> of only video
108+
val audioYtFiles = ytFiles.getVideoOnly() // Return ArrayList<YtFile> of only audio
67109
```
68110
To get best quality of video or audio, you can call this function
69111
```kotlin
70-
GlobalScope.launch {
71-
val ytFiles = YTExtractor(context).getYtFile(videoId)
72-
val videoYtFiles = ytFiles.getAudioOnly()?.bestQuality() // Return best quality video
73-
val audioYtFiles = ytFiles.getVideoOnly()?.bestQuality() // Return best quality audio
74-
}
112+
val ytFiles = yt.getYtFile()
113+
val videoYtFiles = ytFiles.getAudioOnly()?.bestQuality() // Return best quality video
114+
val audioYtFiles = ytFiles.getVideoOnly()?.bestQuality() // Return best quality audio
75115
```
76116

77117
## Not working?

app/src/main/java/com/maxrave/exampleApp/ui/MainActivity.kt

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,47 @@ import android.os.Bundle
55
import android.util.Log
66
import android.widget.TextView
77
import com.maxrave.exampleApp.R
8+
import com.maxrave.kotlinyoutubeextractor.State
89
import com.maxrave.kotlinyoutubeextractor.YTExtractor
910
import com.maxrave.kotlinyoutubeextractor.bestQuality
1011
import com.maxrave.kotlinyoutubeextractor.getAudioOnly
1112
import com.maxrave.kotlinyoutubeextractor.getVideoOnly
13+
import kotlinx.coroutines.DelicateCoroutinesApi
1214
import kotlinx.coroutines.Dispatchers
1315
import kotlinx.coroutines.GlobalScope
1416
import kotlinx.coroutines.launch
1517
import kotlinx.coroutines.withContext
1618

1719
class MainActivity : AppCompatActivity() {
20+
@OptIn(DelicateCoroutinesApi::class)
1821
override fun onCreate(savedInstanceState: Bundle?) {
1922
super.onCreate(savedInstanceState)
2023
setContentView(R.layout.activity_main)
21-
24+
val videoId = "d40rzwlq8l4"
2225
val tv = findViewById<TextView>(R.id.textView)
26+
var a: String
27+
val yt = YTExtractor(this@MainActivity)
2328
GlobalScope.launch {
24-
var a = ""
25-
YTExtractor(this@MainActivity).getYtFile("lO816281lJQ").let { it ->
26-
a = it?.get(22).let { data ->
27-
data?.url.toString()
28-
}
29-
Log.d("Test get Stream URL", "Itag 22 URL: $a")
30-
Log.d("Test Audio Filter", "Audio Only: ${it?.getAudioOnly()}")
31-
Log.d("Test video filter", "Video Only: ${it?.getVideoOnly()}")
32-
33-
Log.d("Test get best quality of Audio", "Best Quality: ${it?.getAudioOnly()?.bestQuality()}")
29+
yt.extract(videoId)
30+
if (yt.state == State.SUCCESS) {
31+
yt.getYTFiles().let { it ->
32+
a = it?.get(251).let { data ->
33+
data?.url.toString()
34+
}
35+
tv.text = a
36+
Log.d("Test get Stream URL", "Itag 22 URL: $a")
37+
Log.d("Test Audio Filter", "Audio Only: ${it?.getAudioOnly()}")
38+
Log.d("Test video filter", "Video Only: ${it?.getVideoOnly()}")
3439

35-
36-
37-
withContext(Dispatchers.Main){
38-
tv.text = it.toString()
40+
Log.d("Test get best quality of Audio", "Best Quality: ${it?.getAudioOnly()?.bestQuality()}")
41+
}
42+
yt.getVideoMeta().let { meta ->
43+
Log.d("Test get Video Meta", "Video Meta: $meta")
44+
Log.d("Test get Video Meta", "Video Meta: ${meta?.maxResImageUrl}")
45+
Log.d("Test get Video Meta", "Video Meta: ${meta?.hqImageUrl}")
46+
Log.d("Test get Video Meta", "Video Meta: ${meta?.mqImageUrl}")
47+
Log.d("Test get Video Meta", "Video Meta: ${meta?.sdImageUrl}")
48+
Log.d("Test get Video Meta", "Video Meta: ${meta?.thumbUrl}")
3949
}
4050
}
4151
}

kotlinYoutubeExtractor/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
id 'maven-publish'
55
}
66
group = 'com.github.maxrave-dev'
7-
version = '0.0.2'
7+
version = '0.0.3'
88

99
android {
1010
namespace 'com.maxrave.kotlinyoutubeextractor'
@@ -59,7 +59,7 @@ afterEvaluate {
5959
from components.release
6060
groupId = 'com.github.maxrave-dev'
6161
artifactId = 'kotlin-youtubeExtractor'
62-
version = '0.0.2'
62+
version = '0.0.3'
6363
}
6464
}
6565
}

kotlinYoutubeExtractor/src/main/java/com/maxrave/kotlinyoutubeextractor/VideoMeta.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.maxrave.kotlinyoutubeextractor
22

3+
/**
4+
* VideoMeta contains all the information available for a YouTube video such as title, author, thumbnail, view count, etc.
5+
*/
36
class VideoMeta(
47
val videoId: String?, val title: String?, val author: String?, val channelId: String?,
58
/**

kotlinYoutubeExtractor/src/main/java/com/maxrave/kotlinyoutubeextractor/YTExtractor.kt

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class YTExtractor(val con: Context) {
4141
val scope = GlobalScope
4242
val job = Job()
4343

44+
var ytFiles: SparseArray<YtFile>? = null
45+
var state: State = State.INIT
46+
4447
var CACHING = true
4548
var LOGGING = false
4649

@@ -62,7 +65,8 @@ class YTExtractor(val con: Context) {
6265
private val lock: Lock = ReentrantLock()
6366
private val jsExecuting = lock.newCondition()
6467

65-
private val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
68+
private val USER_AGENT =
69+
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
6670
//Old User Agent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.98 Safari/537.36"
6771

6872
private val patYouTubePageLink =
@@ -207,7 +211,6 @@ class YTExtractor(val con: Context) {
207211
}
208212

209213

210-
211214
@Throws(
212215
IOException::class,
213216
InterruptedException::class,
@@ -359,6 +362,7 @@ class YTExtractor(val con: Context) {
359362
}
360363
return ytFiles
361364
}
365+
362366
@Throws(IOException::class)
363367
private fun decipherSignature(encSignatures: SparseArray<String>): Boolean {
364368
// Assume the functions don't change that much
@@ -486,6 +490,7 @@ class YTExtractor(val con: Context) {
486490
}
487491
return true
488492
}
493+
489494
private fun readDecipherFunctFromCache() {
490495
val cacheFile = File(cacheDirPath + "/" + CACHE_FILE_NAME)
491496
// The cached functions are valid for 2 weeks
@@ -509,6 +514,7 @@ class YTExtractor(val con: Context) {
509514
}
510515
}
511516
}
517+
512518
private fun decipherViaWebView(encSignatures: SparseArray<String>) {
513519
val context = refContext!!.get() ?: return
514520
val stb = StringBuilder(decipherFunctions + " function decipher(")
@@ -551,6 +557,7 @@ class YTExtractor(val con: Context) {
551557
})
552558
}
553559
}
560+
554561
fun setDefaultHttpProtocol(useHttp: Boolean) {}
555562

556563
private fun writeDeciperFunctToChache() {
@@ -583,16 +590,16 @@ class YTExtractor(val con: Context) {
583590
}
584591
}
585592
}
593+
586594
/**
587-
* Return a sparse array of available YtFiles in different formats
595+
* Extract data from YouTube videoId
588596
* Using Kotlin Coroutines to call this function
589-
* To get specify YtFile use .get(itag) function
590597
* @param videoId YouTube videoId
591-
* @return SparseArray YtFile
592598
*/
593599
@OptIn(DelicateCoroutinesApi::class)
594-
suspend fun getYtFile(videoId: String): SparseArray<YtFile>?{
595-
return scope.async {
600+
suspend fun extract(videoId: String) {
601+
ytFiles = scope.async {
602+
state = State.LOADING
596603
var mat = patYouTubePageLink.matcher(videoId)
597604
if (mat.find()) {
598605
videoID = mat.group(3)
@@ -606,16 +613,37 @@ class YTExtractor(val con: Context) {
606613
}
607614
if (videoID != null) {
608615
try {
609-
return@async getStreamUrls()!!
616+
state = State.SUCCESS
617+
return@async getStreamUrls()
610618
} catch (e: java.lang.Exception) {
619+
state = State.ERROR
611620
Log.e(LOG_TAG, "Extraction failed", e)
612621
}
613622
} else {
623+
state = State.ERROR
614624
Log.e(LOG_TAG, "Wrong YouTube link format")
615625
}
616626
return@async null
617627
}.await()
618628
}
629+
630+
/**
631+
* After extract, you can get the video meta data if state is SUCCESS
632+
* Please check the state before call this function
633+
* @return videoMeta YouTube Video Metadata
634+
*/
635+
fun getVideoMeta(): VideoMeta? {
636+
return videoMeta
637+
}
638+
/**
639+
* After extract, you can get the video stream URL data if state is SUCCESS
640+
* Please check the state before call this function
641+
* To get stream URL, use [.get(itag)]
642+
* @return ytFiles SparseArray of YtFile
643+
*/
644+
fun getYTFiles(): SparseArray<YtFile>? {
645+
return ytFiles
646+
}
619647
}
620648

621649
/**
@@ -657,4 +685,12 @@ fun <T> SparseArray<T>.values(): ArrayList<T> {
657685
}
658686
fun ArrayList<YtFile>.bestQuality(): YtFile?{
659687
return this.maxByOrNull { it.meta!!.audioBitrate }
688+
}
689+
690+
691+
enum class State {
692+
SUCCESS,
693+
ERROR,
694+
LOADING,
695+
INIT,
660696
}

0 commit comments

Comments
 (0)