This commit is contained in:
sifacai@outlook.com 2022-10-20 22:04:43 +08:00
commit 86d795648d
106 changed files with 6440 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

19
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

52
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/ic_outline_play_circle_outline_128.xml" value="0.1555" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/played_percentage.xml" value="0.1765" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/popmenu_focus.xml" value="0.1555" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/shape_user_focus_vholder.xml" value="0.1555" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/activity_detail.xml" value="0.14583333333333334" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/activity_search.xml" value="0.14583333333333334" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/activity_vlc_player.xml" value="0.286231884057971" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/dialog_login.xml" value="0.14479166666666668" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/item_h.xml" value="0.16770833333333332" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/item_v.xml" value="0.14583333333333334" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/popmenu.xml" value="0.14479166666666668" />
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/popmenu_item.xml" value="0.14479166666666668" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/img_loading_placeholder.xml" value="0.1625" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/played_percentage.xml" value="0.1615" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/popmenu_focus.xml" value="0.1625" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/shape_user_focus.xml" value="0.1625" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/shape_user_focus_VH.xml" value="0.1625" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/shape_user_focus_vholder.xml" value="0.1625" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activebar_custom.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_collection.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_detail.xml" value="0.1" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_main.xml" value="0.2957427536231884" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_search.xml" value="0.14791666666666667" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_vlc_player.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/dialog_login.xml" value="0.4" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/home_horizon_tvrecycler.xml" value="0.14791666666666667" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/item_h.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/item_title.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/item_v.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/jellyfin_home.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/loading.xml" value="0.2957427536231884" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/loading_alert.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/menu_item.xml" value="0.2957427536231884" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/popmenu.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/popmenu_item.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/menu/activebar_menu.xml" value="0.14895833333333333" />
<entry key="..\:/work/VlcJellyfin/app/src/main/res/menu/activebar_sort_menu.xml" value="0.14895833333333333" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

5
README.md Normal file
View File

@ -0,0 +1,5 @@
jellyfin安卓TV客户端支持安卓4.2以上。
在安卓5.1及以下环境中运行时集成的VLC播放器可能无法播放HTTPS可在右上角菜单勾选"调用外部播放器"选项,使用外部播放器播放。
支持jellyfin8.1以上

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

53
app/build.gradle Normal file
View File

@ -0,0 +1,53 @@
plugins {
id 'com.android.application'
}
android {
compileSdk 32
defaultConfig {
applicationId "org.sifacai.vlcjellyfin"
minSdk 17
targetSdk 32
versionCode 1
versionName "1.0.5"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
splits {
abi {
enable true
include 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
//implementation files('libs/libvlc-all-3.4.9.aar')
implementation 'org.videolan.android:libvlc-all:3.5.1'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'com.lzy.net:okgo:3.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.google.code.gson:gson:2.9.1'
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
implementation 'com.owen:tv-recyclerview:3.0.0'
implementation 'com.squareup.picasso:picasso:2.8'
implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package org.sifacai.vlcjellyfin;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("org.sifacai.vlcjellyfin", appContext.getPackageName());
}
}

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.sifacai.vlcjellyfin">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/touchicon144"
android:label="@string/app_name"
android:roundIcon="@drawable/touchicon144"
android:supportsRtl="true"
android:theme="@style/jellyfin"
android:usesCleartextTraffic="true"
android:hardwareAccelerated="true"
tools:targetApi="31">
<activity
android:name=".HomeActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DetailActivity"
android:exported="false" />
<activity
android:name=".CollectionActivity"
android:exported="false" />
<activity
android:name=".SearchActivity"
android:exported="false" />
<activity
android:name=".VlcPlayerActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat.NoActionBar"/>
<meta-data
android:name="design_width_in_dp"
android:value="1280" />
<meta-data
android:name="design_height_in_dp"
android:value="720" />
</application>
</manifest>

View File

@ -0,0 +1,236 @@
package org.sifacai.vlcjellyfin;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.Toast;
import androidx.annotation.Nullable;
import me.jessyan.autosize.internal.CustomAdapt;
public class BaseActivity extends AppCompatActivity implements CustomAdapt {
public AppCompatActivity mAA = this;
private ProgressDialog progressDialog;
private ImageView activeBarBack;
private ImageView actionBarSetBtn;
private PopupMenu settingMenu;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar();
if (null != actionBar) {
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
actionBar.setCustomView(R.layout.activebar_custom);
activeBarBack = findViewById(R.id.actionBar_back);
activeBarBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mAA.finish();
}
});
ImageView activeBarSearch = findViewById(R.id.actionBar_searchBtn);
activeBarSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(mAA,SearchActivity.class);
startActivity(intent);
}
});
//actionBar设置菜单
actionBarSetBtn = findViewById(R.id.actionBar_setBtn);
actionBarSetBtn.setOnClickListener(actionBarSetBtnOnclick);
settingMenu = new PopupMenu(this,actionBarSetBtn);
settingMenu.getMenuInflater().inflate(R.menu.activebar_menu,settingMenu.getMenu());
settingMenu.setOnMenuItemClickListener(settingMenuItemOnclick);
}
}
private View.OnClickListener actionBarSetBtnOnclick = new View.OnClickListener() {
@Override
public void onClick(View view) {
settingMenu.show();
Menu setmenu = settingMenu.getMenu();
setmenu.findItem(R.id.actionBar_option_PlayStartInBegin).setChecked(JfClient.config.isPlayStartInBegin());
setmenu.findItem(R.id.actionBar_option_HAACC).setChecked(JfClient.config.isHAACC());
setmenu.findItem(R.id.actionBar_option_FORCE_HAACC).setChecked(JfClient.config.isFORCE_HAACC());
setmenu.findItem(R.id.actionBar_option_ExtensionPlayer).setChecked(JfClient.config.isExtensionPlayer());
}
};
private PopupMenu.OnMenuItemClickListener settingMenuItemOnclick = new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
int menuid = menuItem.getItemId();
switch (menuid){
case R.id.activeBar_option_logout:
logout();
break;
case R.id.actionBar_option_HAACC:
JfClient.config.setHAACC(!JfClient.config.isHAACC());
break;
case R.id.actionBar_option_FORCE_HAACC:
JfClient.config.setFORCE_HAACC(!JfClient.config.isFORCE_HAACC());
break;
case R.id.actionBar_option_PlayStartInBegin:
JfClient.config.setPlayStartInBegin(!JfClient.config.isPlayStartInBegin());
break;
case R.id.actionBar_option_ExtensionPlayer:
JfClient.config.setExtensionPlayer(!JfClient.config.isExtensionPlayer());
break;
}
return true;
}
};
// @Override
// public boolean onCreateOptionsMenu(@NonNull Menu menu) {
// getMenuInflater().inflate(R.menu.activebar_menu, menu);
// return super.onCreateOptionsMenu(menu);
// }
//
// @Override
// public boolean onOptionsItemSelected(@NonNull MenuItem item) {
// if (item.getItemId() == R.id.activeBar_option_logout) {
// logout();
// }
// return super.onOptionsItemSelected(item);
// }
/**
* 禁用标题栏返回按钮
*/
public void disableActiveBarBack() {
if(null != activeBarBack)
activeBarBack.setVisibility(View.GONE);
}
/**
* 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选择一个作为基准进行适配)
*
* @return {@code true} 为按照宽度进行适配, {@code false} 为按照高度进行适配
*/
@Override
public boolean isBaseOnWidth() {
return true;
}
/**
* 返回设计图上的设计尺寸
* {@link #getSizeInDp} 须配合 {@link #isBaseOnWidth()} 使用, 规则如下:
* 如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link #getSizeInDp} 则应该返回设计图的总宽度
* 如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link #getSizeInDp} 则应该返回设计图的总高度
* 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #getSizeInDp} 则返回 {@code 0}
*
* @return 设计图上的设计尺寸
*/
@Override
public float getSizeInDp() {
return 0;
}
public void showLoadingDialog() {
showLoadingDialog("");
}
/**
* 显示加载动画框
*/
public void showLoadingDialog(String title) {
mAA.runOnUiThread(new Runnable() {
@Override
public void run() {
if(null != progressDialog){
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
progressDialog = null;
}
progressDialog = new ProgressDialog(mAA);
progressDialog.setMessage(title);
progressDialog.show();
progressDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialogInterface, int i, KeyEvent keyEvent) {
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
dismissLoadingDialog();
mAA.finish();
return true;
}
return false;
}
});
}
});
}
/**
* 隐藏加载框
*/
public void dismissLoadingDialog() {
mAA.runOnUiThread(new Runnable() {
@Override
public void run() {
if (null != progressDialog && progressDialog.isShowing()) {
progressDialog.dismiss();
progressDialog = null;
}
}
});
}
/**
* 设置加载框文字
*
* @param text
*/
public void setLoadingText(String text) {
mAA.runOnUiThread(new Runnable() {
@Override
public void run() {
if (null != progressDialog && progressDialog.isShowing()) {
progressDialog.setMessage(text);
}else{
showLoadingDialog(text);
}
}
});
}
public void ShowToask(String msg) {
mAA.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mAA, msg, Toast.LENGTH_LONG).show();
}
});
}
/**
*
*/
private void logout() {
JfClient.config.clear();
JfClient.UserId = "";
JfClient.AccessToken = "";
System.exit(0);
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
import java.util.Date;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class Chapters {
private long StartPositionTicks;
private String Name;
private String ImageDateModified;
public void setStartPositionTicks(long StartPositionTicks) {
this.StartPositionTicks = StartPositionTicks;
}
public long getStartPositionTicks() {
return StartPositionTicks;
}
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
public void setImageDateModified(String ImageDateModified) {
this.ImageDateModified = ImageDateModified;
}
public String getImageDateModified() {
return ImageDateModified;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class ExternalUrls {
private String Name;
private String Url;
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
public void setUrl(String Url) {
this.Url = Url;
}
public String getUrl() {
return Url;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class GenreItems {
private String Name;
private String Id;
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
public void setId(String Id) {
this.Id = Id;
}
public String getId() {
return Id;
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class ImageTags {
private String Primary;
private String Logo;
private String Thumb;
public void setPrimary(String Primary) {
this.Primary = Primary;
}
public String getPrimary() {
return Primary;
}
public void setLogo(String Logo) {
this.Logo = Logo;
}
public String getLogo() {
return Logo;
}
public void setThumb(String thumb) {
Thumb = thumb;
}
public String getThumb() {
return Thumb;
}
}

View File

@ -0,0 +1,502 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
import java.util.Date;
import java.util.List;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class Item {
private String Name;
private String OriginalTitle;
private String ServerId;
private String Id;
private String Etag;
private Date DateCreated;
private boolean CanDelete;
private boolean CanDownload;
private boolean HasSubtitles;
private String Container;
private String SortName;
private String PremiereDate;
private List<ExternalUrls> ExternalUrls;
private List<MediaSources> MediaSources;
private List<String> ProductionLocations;
private String Path;
private boolean EnableMediaSourceDisplay;
private String OfficialRating;
private String ChannelId;
private String Overview;
private List<String> Taglines;
private List<String> Genres;
private String CommunityRating;
private long RunTimeTicks;
private String PlayAccess;
private String ProductionYear;
// private List<String> RemoteTrailers;
private ProviderIds ProviderIds;
private boolean IsHD;
private boolean IsFolder;
private String ParentId;
private String Type;
private List<People> People;
private List<Studios> Studios;
private List<GenreItems> GenreItems;
private int LocalTrailerCount;
private UserData UserData;
private int SpecialFeatureCount;
private String DisplayPreferencesId;
private List<String> Tags;
private double PrimaryImageAspectRatio;
private List<MediaStreams> MediaStreams;
private String VideoType;
private ImageTags ImageTags;
private List<String> BackdropImageTags;
private List<Chapters> Chapters;
private String LocationType;
private String MediaType;
private List<String> LockedFields;
private boolean LockData;
private int Width;
private int Height;
public int getPartCount() {
return PartCount;
}
public void setPartCount(int partCount) {
PartCount = partCount;
}
private int PartCount;
private String SeriesId;
private String SeriesName;
private String SeasonId;
//private List<String> ProductionLocations;
//private
public String getSeriesId() {
return SeriesId;
}
public void setSeriesId(String seriesId) {
SeriesId = seriesId;
}
public String getSeasonId() {
return SeasonId;
}
public void setSeasonId(String seasonId) {
SeasonId = seasonId;
}
private String SeasonName;
public boolean isHasSubtitles() {
return HasSubtitles;
}
public boolean isHD() {
return IsHD;
}
public void setHD(boolean HD) {
IsHD = HD;
}
public boolean isFolder() {
return IsFolder;
}
public void setFolder(boolean folder) {
IsFolder = folder;
}
public String getSeriesName() {
return SeriesName;
}
public void setSeriesName(String seriesName) {
SeriesName = seriesName;
}
public String getSeasonName() {
return SeasonName;
}
public void setSeasonName(String seasonName) {
SeasonName = seasonName;
}
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
public void setOriginalTitle(String OriginalTitle) {
this.OriginalTitle = OriginalTitle;
}
public String getOriginalTitle() {
return OriginalTitle;
}
public void setServerId(String ServerId) {
this.ServerId = ServerId;
}
public String getServerId() {
return ServerId;
}
public void setId(String Id) {
this.Id = Id;
}
public String getId() {
return Id;
}
public void setEtag(String Etag) {
this.Etag = Etag;
}
public String getEtag() {
return Etag;
}
public void setDateCreated(Date DateCreated) {
this.DateCreated = DateCreated;
}
public Date getDateCreated() {
return DateCreated;
}
public void setCanDelete(boolean CanDelete) {
this.CanDelete = CanDelete;
}
public boolean getCanDelete() {
return CanDelete;
}
public void setCanDownload(boolean CanDownload) {
this.CanDownload = CanDownload;
}
public boolean getCanDownload() {
return CanDownload;
}
public void setHasSubtitles(boolean HasSubtitles) {
this.HasSubtitles = HasSubtitles;
}
public boolean getHasSubtitles() {
return HasSubtitles;
}
public void setContainer(String Container) {
this.Container = Container;
}
public String getContainer() {
return Container;
}
public void setSortName(String SortName) {
this.SortName = SortName;
}
public String getSortName() {
return SortName;
}
public void setPremiereDate(String PremiereDate) {
this.PremiereDate = PremiereDate;
}
public String getPremiereDate() {
return PremiereDate;
}
public void setExternalUrls(List<ExternalUrls> ExternalUrls) {
this.ExternalUrls = ExternalUrls;
}
public List<ExternalUrls> getExternalUrls() {
return ExternalUrls;
}
public void setMediaSources(List<MediaSources> MediaSources) {
this.MediaSources = MediaSources;
}
public List<MediaSources> getMediaSources() {
return MediaSources;
}
public void setProductionLocations(List<String> ProductionLocations) {
this.ProductionLocations = ProductionLocations;
}
public List<String> getProductionLocations() {
return ProductionLocations;
}
public void setPath(String Path) {
this.Path = Path;
}
public String getPath() {
return Path;
}
public void setEnableMediaSourceDisplay(boolean EnableMediaSourceDisplay) {
this.EnableMediaSourceDisplay = EnableMediaSourceDisplay;
}
public boolean getEnableMediaSourceDisplay() {
return EnableMediaSourceDisplay;
}
public void setOfficialRating(String OfficialRating) {
this.OfficialRating = OfficialRating;
}
public String getOfficialRating() {
return OfficialRating;
}
public void setChannelId(String ChannelId) {
this.ChannelId = ChannelId;
}
public String getChannelId() {
return ChannelId;
}
public void setOverview(String Overview) {
this.Overview = Overview;
}
public String getOverview() {
return Overview;
}
public void setTaglines(List<String> Taglines) {
this.Taglines = Taglines;
}
public List<String> getTaglines() {
return Taglines;
}
public void setGenres(List<String> Genres) {
this.Genres = Genres;
}
public List<String> getGenres() {
return Genres;
}
public void setCommunityRating(String CommunityRating) {
this.CommunityRating = CommunityRating;
}
public String getCommunityRating() {
return CommunityRating;
}
public void setRunTimeTicks(long RunTimeTicks) {
this.RunTimeTicks = RunTimeTicks;
}
public long getRunTimeTicks() {
return RunTimeTicks;
}
public void setPlayAccess(String PlayAccess) {
this.PlayAccess = PlayAccess;
}
public String getPlayAccess() {
return PlayAccess;
}
public void setProductionYear(String ProductionYear) {
this.ProductionYear = ProductionYear;
}
public String getProductionYear() {
return ProductionYear;
}
// public void setRemoteTrailers(List<String> RemoteTrailers) {
// this.RemoteTrailers = RemoteTrailers;
// }
// public List<String> getRemoteTrailers() {
// return RemoteTrailers;
// }
public void setProviderIds(ProviderIds ProviderIds) {
this.ProviderIds = ProviderIds;
}
public ProviderIds getProviderIds() {
return ProviderIds;
}
public void setIsHD(boolean IsHD) {
this.IsHD = IsHD;
}
public boolean getIsHD() {
return IsHD;
}
public void setIsFolder(boolean IsFolder) {
this.IsFolder = IsFolder;
}
public boolean getIsFolder() {
return IsFolder;
}
public void setParentId(String ParentId) {
this.ParentId = ParentId;
}
public String getParentId() {
return ParentId;
}
public void setType(String Type) {
this.Type = Type;
}
public String getType() {
return Type;
}
public void setPeople(List<People> People) {
this.People = People;
}
public List<People> getPeople() {
return People;
}
public void setStudios(List<Studios> Studios) {
this.Studios = Studios;
}
public List<Studios> getStudios() {
return Studios;
}
public void setGenreItems(List<GenreItems> GenreItems) {
this.GenreItems = GenreItems;
}
public List<GenreItems> getGenreItems() {
return GenreItems;
}
public void setLocalTrailerCount(int LocalTrailerCount) {
this.LocalTrailerCount = LocalTrailerCount;
}
public int getLocalTrailerCount() {
return LocalTrailerCount;
}
public void setUserData(UserData UserData) {
this.UserData = UserData;
}
public UserData getUserData() {
return UserData;
}
public void setSpecialFeatureCount(int SpecialFeatureCount) {
this.SpecialFeatureCount = SpecialFeatureCount;
}
public int getSpecialFeatureCount() {
return SpecialFeatureCount;
}
public void setDisplayPreferencesId(String DisplayPreferencesId) {
this.DisplayPreferencesId = DisplayPreferencesId;
}
public String getDisplayPreferencesId() {
return DisplayPreferencesId;
}
public void setTags(List<String> Tags) {
this.Tags = Tags;
}
public List<String> getTags() {
return Tags;
}
public void setPrimaryImageAspectRatio(double PrimaryImageAspectRatio) {
this.PrimaryImageAspectRatio = PrimaryImageAspectRatio;
}
public double getPrimaryImageAspectRatio() {
return PrimaryImageAspectRatio;
}
public void setMediaStreams(List<MediaStreams> MediaStreams) {
this.MediaStreams = MediaStreams;
}
public List<MediaStreams> getMediaStreams() {
return MediaStreams;
}
public void setVideoType(String VideoType) {
this.VideoType = VideoType;
}
public String getVideoType() {
return VideoType;
}
public void setImageTags(ImageTags ImageTags) {
this.ImageTags = ImageTags;
}
public ImageTags getImageTags() {
return ImageTags;
}
public void setBackdropImageTags(List<String> BackdropImageTags) {
this.BackdropImageTags = BackdropImageTags;
}
public List<String> getBackdropImageTags() {
return BackdropImageTags;
}
public void setChapters(List<Chapters> Chapters) {
this.Chapters = Chapters;
}
public List<Chapters> getChapters() {
return Chapters;
}
public void setLocationType(String LocationType) {
this.LocationType = LocationType;
}
public String getLocationType() {
return LocationType;
}
public void setMediaType(String MediaType) {
this.MediaType = MediaType;
}
public String getMediaType() {
return MediaType;
}
public void setLockedFields(List<String> LockedFields) {
this.LockedFields = LockedFields;
}
public List<String> getLockedFields() {
return LockedFields;
}
public void setLockData(boolean LockData) {
this.LockData = LockData;
}
public boolean getLockData() {
return LockData;
}
public void setWidth(int Width) {
this.Width = Width;
}
public int getWidth() {
return Width;
}
public void setHeight(int Height) {
this.Height = Height;
}
public int getHeight() {
return Height;
}
}

View File

@ -0,0 +1,37 @@
package org.sifacai.vlcjellyfin.Bean;
import java.util.List;
public class Items {
private List<Item> Items;
private int StartIndex;
private int TotalRecordCount;
public List<Item> getItems() {
return Items;
}
public void setItems(List<Item> items) {
Items = items;
}
public void AddItems(List<Item> items) {
Items.addAll(items);
}
public int getStartIndex() {
return StartIndex;
}
public void setStartIndex(int startIndex) {
StartIndex = startIndex;
}
public int getTotalRecordCount() {
return TotalRecordCount;
}
public void setTotalRecordCount(int totalRecordCount) {
TotalRecordCount = totalRecordCount;
}
}

View File

@ -0,0 +1,49 @@
package org.sifacai.vlcjellyfin.Bean;
public class MediaAttachments {
private String Codec;
private String CodecTag;
private int Index;
private String FileName;
private String MimeType;
public String getCodec() {
return Codec;
}
public void setCodec(String codec) {
Codec = codec;
}
public String getCodecTag() {
return CodecTag;
}
public void setCodecTag(String codecTag) {
CodecTag = codecTag;
}
public int getIndex() {
return Index;
}
public void setIndex(int index) {
Index = index;
}
public String getFileName() {
return FileName;
}
public void setFileName(String fileName) {
FileName = fileName;
}
public String getMimeType() {
return MimeType;
}
public void setMimeType(String mimeType) {
MimeType = mimeType;
}
}

View File

@ -0,0 +1,247 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
import java.util.List;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class MediaSources {
private String Protocol;
private String Id;
private String Path;
private String Type;
private String Container;
private long Size;
private String Name;
private boolean IsRemote;
private String ETag;
private long RunTimeTicks;
private boolean ReadAtNativeFramerate;
private boolean IgnoreDts;
private boolean IgnoreIndex;
private boolean GenPtsInput;
private boolean SupportsTranscoding;
private boolean SupportsDirectStream;
private boolean SupportsDirectPlay;
private boolean IsInfiniteStream;
private boolean RequiresOpening;
private boolean RequiresClosing;
private boolean RequiresLooping;
private boolean SupportsProbing;
private String VideoType;
private List<MediaStreams> MediaStreams;
private List<MediaAttachments> MediaAttachments;
private List<String> Formats;
private long Bitrate;
private int DefaultAudioStreamIndex;
private int DefaultSubtitleStreamIndex;
public void setProtocol(String Protocol) {
this.Protocol = Protocol;
}
public String getProtocol() {
return Protocol;
}
public void setId(String Id) {
this.Id = Id;
}
public String getId() {
return Id;
}
public void setPath(String Path) {
this.Path = Path;
}
public String getPath() {
return Path;
}
public void setType(String Type) {
this.Type = Type;
}
public String getType() {
return Type;
}
public void setContainer(String Container) {
this.Container = Container;
}
public String getContainer() {
return Container;
}
public void setSize(long Size) {
this.Size = Size;
}
public long getSize() {
return Size;
}
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
public void setIsRemote(boolean IsRemote) {
this.IsRemote = IsRemote;
}
public boolean getIsRemote() {
return IsRemote;
}
public void setETag(String ETag) {
this.ETag = ETag;
}
public String getETag() {
return ETag;
}
public void setRunTimeTicks(long RunTimeTicks) {
this.RunTimeTicks = RunTimeTicks;
}
public long getRunTimeTicks() {
return RunTimeTicks;
}
public void setReadAtNativeFramerate(boolean ReadAtNativeFramerate) {
this.ReadAtNativeFramerate = ReadAtNativeFramerate;
}
public boolean getReadAtNativeFramerate() {
return ReadAtNativeFramerate;
}
public void setIgnoreDts(boolean IgnoreDts) {
this.IgnoreDts = IgnoreDts;
}
public boolean getIgnoreDts() {
return IgnoreDts;
}
public void setIgnoreIndex(boolean IgnoreIndex) {
this.IgnoreIndex = IgnoreIndex;
}
public boolean getIgnoreIndex() {
return IgnoreIndex;
}
public void setGenPtsInput(boolean GenPtsInput) {
this.GenPtsInput = GenPtsInput;
}
public boolean getGenPtsInput() {
return GenPtsInput;
}
public void setSupportsTranscoding(boolean SupportsTranscoding) {
this.SupportsTranscoding = SupportsTranscoding;
}
public boolean getSupportsTranscoding() {
return SupportsTranscoding;
}
public void setSupportsDirectStream(boolean SupportsDirectStream) {
this.SupportsDirectStream = SupportsDirectStream;
}
public boolean getSupportsDirectStream() {
return SupportsDirectStream;
}
public void setSupportsDirectPlay(boolean SupportsDirectPlay) {
this.SupportsDirectPlay = SupportsDirectPlay;
}
public boolean getSupportsDirectPlay() {
return SupportsDirectPlay;
}
public void setIsInfiniteStream(boolean IsInfiniteStream) {
this.IsInfiniteStream = IsInfiniteStream;
}
public boolean getIsInfiniteStream() {
return IsInfiniteStream;
}
public void setRequiresOpening(boolean RequiresOpening) {
this.RequiresOpening = RequiresOpening;
}
public boolean getRequiresOpening() {
return RequiresOpening;
}
public void setRequiresClosing(boolean RequiresClosing) {
this.RequiresClosing = RequiresClosing;
}
public boolean getRequiresClosing() {
return RequiresClosing;
}
public void setRequiresLooping(boolean RequiresLooping) {
this.RequiresLooping = RequiresLooping;
}
public boolean getRequiresLooping() {
return RequiresLooping;
}
public void setSupportsProbing(boolean SupportsProbing) {
this.SupportsProbing = SupportsProbing;
}
public boolean getSupportsProbing() {
return SupportsProbing;
}
public void setVideoType(String VideoType) {
this.VideoType = VideoType;
}
public String getVideoType() {
return VideoType;
}
public void setMediaStreams(List<MediaStreams> MediaStreams) {
this.MediaStreams = MediaStreams;
}
public List<MediaStreams> getMediaStreams() {
return MediaStreams;
}
public void setMediaAttachments(List<MediaAttachments> MediaAttachments) {
this.MediaAttachments = MediaAttachments;
}
public List<MediaAttachments> getMediaAttachments() {
return MediaAttachments;
}
public void setFormats(List<String> Formats) {
this.Formats = Formats;
}
public List<String> getFormats() {
return Formats;
}
public void setBitrate(long Bitrate) {
this.Bitrate = Bitrate;
}
public long getBitrate() {
return Bitrate;
}
public void setDefaultAudioStreamIndex(int DefaultAudioStreamIndex) {
this.DefaultAudioStreamIndex = DefaultAudioStreamIndex;
}
public int getDefaultAudioStreamIndex() {
return DefaultAudioStreamIndex;
}
public void setDefaultSubtitleStreamIndex(int DefaultSubtitleStreamIndex) {
this.DefaultSubtitleStreamIndex = DefaultSubtitleStreamIndex;
}
public int getDefaultSubtitleStreamIndex() {
return DefaultSubtitleStreamIndex;
}
}

View File

@ -0,0 +1,247 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
import java.util.Date;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class MediaStreams {
private String Codec;
private String Language;
private String ColorSpace;
private String ColorTransfer;
private String ColorPrimaries;
private String TimeBase;
private String Title;
private String VideoRange;
private String VideoRangeType;
private String DisplayTitle;
private boolean IsInterlaced;
private long BitRate;
private int BitDepth;
private int RefFrames;
private boolean IsDefault;
private boolean IsForced;
private int Height;
private int Width;
private double AverageFrameRate;
private double RealFrameRate;
private String Profile;
private String Type;
private String AspectRatio;
private int Index;
private boolean IsExternal;
private boolean IsTextSubtitleStream;
private boolean SupportsExternalStream;
private String PixelFormat;
private int Level;
public void setCodec(String Codec) {
this.Codec = Codec;
}
public String getCodec() {
return Codec;
}
public void setLanguage(String Language) {
this.Language = Language;
}
public String getLanguage() {
return Language;
}
public void setColorSpace(String ColorSpace) {
this.ColorSpace = ColorSpace;
}
public String getColorSpace() {
return ColorSpace;
}
public void setColorTransfer(String ColorTransfer) {
this.ColorTransfer = ColorTransfer;
}
public String getColorTransfer() {
return ColorTransfer;
}
public void setColorPrimaries(String ColorPrimaries) {
this.ColorPrimaries = ColorPrimaries;
}
public String getColorPrimaries() {
return ColorPrimaries;
}
public void setTimeBase(String TimeBase) {
this.TimeBase = TimeBase;
}
public String getTimeBase() {
return TimeBase;
}
public void setTitle(String Title) {
this.Title = Title;
}
public String getTitle() {
return Title;
}
public void setVideoRange(String VideoRange) {
this.VideoRange = VideoRange;
}
public String getVideoRange() {
return VideoRange;
}
public void setVideoRangeType(String VideoRangeType) {
this.VideoRangeType = VideoRangeType;
}
public String getVideoRangeType() {
return VideoRangeType;
}
public void setDisplayTitle(String DisplayTitle) {
this.DisplayTitle = DisplayTitle;
}
public String getDisplayTitle() {
return DisplayTitle;
}
public void setIsInterlaced(boolean IsInterlaced) {
this.IsInterlaced = IsInterlaced;
}
public boolean getIsInterlaced() {
return IsInterlaced;
}
public void setBitRate(long BitRate) {
this.BitRate = BitRate;
}
public long getBitRate() {
return BitRate;
}
public void setBitDepth(int BitDepth) {
this.BitDepth = BitDepth;
}
public int getBitDepth() {
return BitDepth;
}
public void setRefFrames(int RefFrames) {
this.RefFrames = RefFrames;
}
public int getRefFrames() {
return RefFrames;
}
public void setIsDefault(boolean IsDefault) {
this.IsDefault = IsDefault;
}
public boolean getIsDefault() {
return IsDefault;
}
public void setIsForced(boolean IsForced) {
this.IsForced = IsForced;
}
public boolean getIsForced() {
return IsForced;
}
public void setHeight(int Height) {
this.Height = Height;
}
public int getHeight() {
return Height;
}
public void setWidth(int Width) {
this.Width = Width;
}
public int getWidth() {
return Width;
}
public void setAverageFrameRate(double AverageFrameRate) {
this.AverageFrameRate = AverageFrameRate;
}
public double getAverageFrameRate() {
return AverageFrameRate;
}
public void setRealFrameRate(double RealFrameRate) {
this.RealFrameRate = RealFrameRate;
}
public double getRealFrameRate() {
return RealFrameRate;
}
public void setProfile(String Profile) {
this.Profile = Profile;
}
public String getProfile() {
return Profile;
}
public void setType(String Type) {
this.Type = Type;
}
public String getType() {
return Type;
}
public void setAspectRatio(String AspectRatio) {
this.AspectRatio = AspectRatio;
}
public String getAspectRatio() {
return AspectRatio;
}
public void setIndex(int Index) {
this.Index = Index;
}
public int getIndex() {
return Index;
}
public void setIsExternal(boolean IsExternal) {
this.IsExternal = IsExternal;
}
public boolean getIsExternal() {
return IsExternal;
}
public void setIsTextSubtitleStream(boolean IsTextSubtitleStream) {
this.IsTextSubtitleStream = IsTextSubtitleStream;
}
public boolean getIsTextSubtitleStream() {
return IsTextSubtitleStream;
}
public void setSupportsExternalStream(boolean SupportsExternalStream) {
this.SupportsExternalStream = SupportsExternalStream;
}
public boolean getSupportsExternalStream() {
return SupportsExternalStream;
}
public void setPixelFormat(String PixelFormat) {
this.PixelFormat = PixelFormat;
}
public String getPixelFormat() {
return PixelFormat;
}
public void setLevel(int Level) {
this.Level = Level;
}
public int getLevel() {
return Level;
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class People {
private String Name;
private String Id;
private String Role;
private String Type;
private String PrimaryImageTag;
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
public void setId(String Id) {
this.Id = Id;
}
public String getId() {
return Id;
}
public void setRole(String Role) {
this.Role = Role;
}
public String getRole() {
return Role;
}
public void setType(String Type) {
this.Type = Type;
}
public String getType() {
return Type;
}
public void setPrimaryImageTag(String PrimaryImageTag) {
this.PrimaryImageTag = PrimaryImageTag;
}
public String getPrimaryImageTag() {
return PrimaryImageTag;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class ProviderIds {
private String Tmdb;
private String Imdb;
public void setTmdb(String Tmdb) {
this.Tmdb = Tmdb;
}
public String getTmdb() {
return Tmdb;
}
public void setImdb(String Imdb) {
this.Imdb = Imdb;
}
public String getImdb() {
return Imdb;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class Studios {
private String Name;
private String Id;
public void setName(String Name) {
this.Name = Name;
}
public String getName() {
return Name;
}
public void setId(String Id) {
this.Id = Id;
}
public String getId() {
return Id;
}
}

View File

@ -0,0 +1,85 @@
/**
* Copyright 2022 json.cn
*/
package org.sifacai.vlcjellyfin.Bean;
import java.util.Date;
/**
* Auto-generated: 2022-09-01 14:6:30
*
* @author json.cn (i@json.cn)
* @website http://www.json.cn/java2pojo/
*/
public class UserData {
private long PlaybackPositionTicks;
private int PlayCount;
private boolean IsFavorite;
private Date LastPlayedDate;
private boolean Played;
private String Key;
private double PlayedPercentage;
public boolean isFavorite() {
return IsFavorite;
}
public void setFavorite(boolean favorite) {
IsFavorite = favorite;
}
public boolean isPlayed() {
return Played;
}
public double getPlayedPercentage() {
return PlayedPercentage;
}
public void setPlayedPercentage(double playedPercentage) {
PlayedPercentage = playedPercentage;
}
public void setPlaybackPositionTicks(long PlaybackPositionTicks) {
this.PlaybackPositionTicks = PlaybackPositionTicks;
}
public long getPlaybackPositionTicks() {
return PlaybackPositionTicks;
}
public void setPlayCount(int PlayCount) {
this.PlayCount = PlayCount;
}
public int getPlayCount() {
return PlayCount;
}
public void setIsFavorite(boolean IsFavorite) {
this.IsFavorite = IsFavorite;
}
public boolean getIsFavorite() {
return IsFavorite;
}
public void setLastPlayedDate(Date LastPlayedDate) {
this.LastPlayedDate = LastPlayedDate;
}
public Date getLastPlayedDate() {
return LastPlayedDate;
}
public void setPlayed(boolean Played) {
this.Played = Played;
}
public boolean getPlayed() {
return Played;
}
public void setKey(String Key) {
this.Key = Key;
}
public String getKey() {
return Key;
}
}

View File

@ -0,0 +1,212 @@
package org.sifacai.vlcjellyfin;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.owen.tvrecyclerview.widget.TvRecyclerView;
import com.owen.tvrecyclerview.widget.V7GridLayoutManager;
import org.sifacai.vlcjellyfin.Bean.Item;
import org.sifacai.vlcjellyfin.Bean.Items;
import java.util.ArrayList;
import java.util.List;
public class CollectionActivity extends BaseActivity {
private String TAG = "CollectionActivity";
private TvRecyclerView mGridContiner = null;
private TextView tvTitleTip = null;
private String ItemId = "";
private int currentPage = 1; //当前页码
private int countPage = 1; //总页数
private int limit = 60; //每页条目
private int totalCount = 0; //总条目数
private String Type = "";
private Item currObj = null;
private List<Item> currItems = null;
private JAdapter currAdapter = null;
private TextView sortMenuBtn;
private PopupMenu SortByMenu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_collection);
if (JfClient.UserId.equals("") || JfClient.AccessToken.equals("")) {
finish();
}
mGridContiner = findViewById(R.id.mGridView);
tvTitleTip = findViewById(R.id.actionBar_titleTip);
V7GridLayoutManager v7GridLayoutManager = new V7GridLayoutManager(this,6);
mGridContiner.setLayoutManager(v7GridLayoutManager);
mGridContiner.setItemAnimator(null); //防崩溃
init();
}
private void init() {
Intent intent = getIntent();
ItemId = intent.getStringExtra("itemId");
if(ItemId.equals("")){
finish();
}else{
currItems = new ArrayList<>();
currAdapter = getJAdapter(currItems);
mGridContiner.setAdapter(currAdapter);
initData();
initSortByMenu();
}
}
public void initData() {
showLoadingDialog("加载中…………");
JfClient.GetItemInfo(ItemId,new JfClient.JJCallBack(){
@Override
public void onSuccess(Item Collection) {
currObj = Collection;
Type = Collection.getType();
fillItems();
setLoadMore();
}
},null);
}
/**
* 加载条目数据
*/
private void fillItems(){
JfClient.GetCollection(ItemId,Type,
JfClient.config.getSortBy(),
JfClient.config.getSortOrder(),
limit,currentPage,
new JfClient.JJCallBack(){
@Override
public void onSuccess(Items items) {
totalCount = items.getTotalRecordCount();
countPage = (int) Math.ceil((double) totalCount / limit);
List<Item> Items = items.getItems();
dismissLoadingDialog();
currAdapter.addItems(Items);
setTitleTip();
mGridContiner.finishLoadMore();
}
},null);
}
private JAdapter getJAdapter(List<Item> items){
JAdapter jAdapter = new JAdapter(items,false);
jAdapter.setOnItemClickListener(new JAdapter.OnItemClickListener() {
@Override
public void onClick(Item item) {
String type = item.getType();
String itemId = item.getId();
Intent intent = null;
if(type.equals("Folder") || type.equals("CollectionFolder")){
intent = new Intent(mAA,CollectionActivity.class);
}else{
intent = new Intent(mAA,DetailActivity.class);
}
intent.putExtra("itemId",itemId);
mAA.startActivity(intent);
}
});
return jAdapter;
}
private void setLoadMore(){
mGridContiner.setOnLoadMoreListener(new TvRecyclerView.OnLoadMoreListener() {
@Override
public void onLoadMore() {
if(currentPage < countPage){
currentPage += 1;
new Thread(new Runnable() {
@Override
public void run() {
fillItems();
}
}).start();
}
}
});
}
private void setTitleTip(){
String tip = ""+ totalCount +" " + countPage + " 页,已加载" + currentPage + "";
tvTitleTip.setText(tip);
}
private void initSortByMenu(){
sortMenuBtn = findViewById(R.id.actionBar_sortBtn);
sortMenuBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int i = 0;
for(Config.SortByType sbt : Config.SortByType.values()){
if(sbt.value.equals(JfClient.config.getSortBy())){
i = sbt.ordinal();
}
}
SortByMenu.show();
}
});
sortMenuBtn.setVisibility(View.VISIBLE);
setSortMenuBtnText();
SortByMenu = new PopupMenu(this,sortMenuBtn);
Menu menu = SortByMenu.getMenu();
Config.SortByType[] Ss = Config.SortByType.values();
for (Config.SortByType sortby:Ss) {
menu.add(0,sortby.ordinal(),sortby.ordinal(),sortby.name());
}
for (Config.SotrOrderType sot:Config.SotrOrderType.values()){
menu.add(1,sot.ordinal() + Ss.length,sot.ordinal() + Ss.length,sot.name());
}
SortByMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
if(menuItem.getGroupId() == 0) {
JfClient.config.setSortBy(Config.SortByType.valueOf(menuItem.getTitle().toString()).value);
}else{
JfClient.config.setSortOrder(Config.SotrOrderType.valueOf(menuItem.getTitle().toString()).value);
}
setSortMenuBtnText();
currAdapter.clearItems();
new Thread(new Runnable() {
@Override
public void run() {
initData();
}
}).start();
return false;
}
});
}
private void setSortMenuBtnText(){
String s = Config.SortByType.findName(JfClient.config.getSortBy());
s += "-";
s += Config.SotrOrderType.findName(JfClient.config.getSortOrder());
sortMenuBtn.setText(s);
}
}

View File

@ -0,0 +1,196 @@
package org.sifacai.vlcjellyfin;
import android.content.Context;
import android.content.SharedPreferences;
public class Config {
private Context context;
private String JellyfinUrl = "";
private String UserName = "";
private String PassWord = "";
private String SortBy = "DateCreated";
private String SortOrder = "Descending";
private boolean PlayStartInBegin; //是否从头开始播放 接上次播放进度
private boolean HAACC; //硬解
private boolean FORCE_HAACC; //强制硬解
private boolean ExtensionPlayer; //调用外部播放器
public boolean isExtensionPlayer() {
return ExtensionPlayer;
}
public void setExtensionPlayer(boolean extensionPlayer) {
ExtensionPlayer = extensionPlayer;
saveConfigToSP("ExtensionPlayer",extensionPlayer);
}
public boolean isPlayStartInBegin() {
return PlayStartInBegin;
}
public void setPlayStartInBegin(boolean playStartInBegin) {
PlayStartInBegin = playStartInBegin;
saveConfigToSP("PlayStartInBegin",playStartInBegin);
}
public Config(Context context) {
this.context = context;
getConfigFromSP();
}
public String getJellyfinUrl() {
return JellyfinUrl;
}
public void setJellyfinUrl(String jellyfinUrl) {
JellyfinUrl = jellyfinUrl;
saveConfigToSP("url",jellyfinUrl);
}
public String getUserName() {
return UserName;
}
public void setUserName(String userName) {
UserName = userName;
saveConfigToSP("username",userName);
}
public String getPassWord() {
return PassWord;
}
public void setPassWord(String passWord) {
PassWord = passWord;
saveConfigToSP("password",passWord);
}
public String getSortBy() {
return SortBy;
}
public void setSortBy(String sortBy) {
SortBy = sortBy;
saveConfigToSP("sortby",sortBy);
}
public String getSortOrder() {
return SortOrder;
}
public void setSortOrder(String sortOrder) {
SortOrder = sortOrder;
saveConfigToSP("sortorder",sortOrder);
}
public boolean isHAACC() {
return HAACC;
}
public void setHAACC(boolean HAACC) {
this.HAACC = HAACC;
saveConfigToSP("HAACC",HAACC);
}
public boolean isFORCE_HAACC() {
return FORCE_HAACC;
}
public void setFORCE_HAACC(boolean FORCE_HAACC) {
this.FORCE_HAACC = FORCE_HAACC;
saveConfigToSP("FORCE_HAACC",FORCE_HAACC);
}
/**
* 读取配置
*/
public void getConfigFromSP() {
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
this.JellyfinUrl = sp.getString("url", "http://");
this.UserName = sp.getString("username", "");
this.PassWord = sp.getString("password", "");
this.SortBy = sp.getString("sortby","DateCreated");
this.SortOrder = sp.getString("sortorder","Descending");
this.HAACC = sp.getBoolean("HAACC",true);
this.FORCE_HAACC = sp.getBoolean("FORCE_HAACC",false);
this.PlayStartInBegin = sp.getBoolean("PlayStartInBegin",true);
this.ExtensionPlayer = sp.getBoolean("ExtensionPlayer",false);
}
/**
* 保存单项配置
*/
public void saveConfigToSP(String key, String value) {
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key, value);
editor.commit();
}
/**
* 保存单项配置
*/
public void saveConfigToSP(String key, boolean value) {
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean(key, value);
editor.commit();
}
/**
* 清除
*/
public void clear() {
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.clear();
editor.commit();
}
public enum SortByType {
评分("CommunityRating"),
加入日期("DateCreated"),
播放日期("DatePlayed"),
家长分级("OfficialRating"),
播放次数("PlayCount"),
发行日期("PremiereDate"),
播放时长("Runtime");
public String value;
SortByType(String value) {
this.value = value;
}
public static String findName(String value){
String name = "";
for (SortByType sbt:SortByType.values()) {
if(sbt.value.equals(value)){
name = sbt.name();
}
}
return name;
}
}
public enum SotrOrderType{
升序("Ascending"),
降序("Descending");
public String value;
SotrOrderType(String value) {
this.value = value;
}
public static String findName(String value){
String name = "";
for (SotrOrderType sot:SotrOrderType.values()) {
if(sot.value.equals(value)){
name = sot.name();
}
}
return name;
}
}
}

View File

@ -0,0 +1,372 @@
package org.sifacai.vlcjellyfin;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
import android.content.UriMatcher;
import android.net.Uri;
import android.os.Bundle;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.webkit.MimeTypeMap;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.owen.tvrecyclerview.widget.V7GridLayoutManager;
import com.owen.tvrecyclerview.widget.V7LinearLayoutManager;
import com.squareup.picasso.Picasso;
import org.sifacai.vlcjellyfin.Bean.ImageTags;
import org.sifacai.vlcjellyfin.Bean.Item;
import org.sifacai.vlcjellyfin.Bean.Items;
import org.sifacai.vlcjellyfin.Bean.MediaStreams;
import org.sifacai.vlcjellyfin.Bean.People;
import org.sifacai.vlcjellyfin.Bean.UserData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class DetailActivity extends BaseActivity implements JAdapter.OnItemClickListener {
private String TAG = "详情:";
private String ItemId;
private ImageView tvCover;
private TextView tvTitle;
private TextView tvDetails;
private ImageView tvPlay;
private JRecyclerView mGridView;
private JRecyclerView mPeopleGridView;
private LinearLayout tvPeopleLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
getSupportActionBar().hide();
if (JfClient.UserId.equals("") || JfClient.AccessToken.equals("")) {
finish();
}
init();
}
private void init() {
tvCover = findViewById(R.id.tvCover);
tvTitle = findViewById(R.id.tvTitle);
tvDetails = findViewById(R.id.tvDetails);
tvPlay = findViewById(R.id.tvPlay);
mGridView = findViewById(R.id.mGridView);
tvPeopleLayout = findViewById(R.id.tvPersonLayout);
mPeopleGridView = findViewById(R.id.mPersonGridView);
Intent intent = getIntent();
ItemId = intent.getStringExtra("itemId");
if (ItemId.equals("")) {
finish();
} else {
showLoadingDialog("加载中……");
initData(ItemId);
}
}
private void initData(String itemId) {
JfClient.GetItemInfo(itemId, new JfClient.JJCallBack() {
@Override
public void onSuccess(Item item) {
fillDetails(item);
}
}, new JfClient.JJCallBack() {
@Override
public void onError(String str) {
errcb.onError(str);
finish();
}
});
}
private void fillDetails(Item details) {
String Id = details.getId();
String Name = details.getName();
String imgurl = JfClient.GetImgUrl(Id, details.getImageTags() == null ? "" : details.getImageTags().getPrimary());
Picasso.get()
.load(imgurl)
.placeholder(R.drawable.img_loading_placeholder)
.error(R.drawable.img_loading_placeholder)
.into(tvCover);
String Genres = String.join(",", details.getGenres());
tvTitle.setText(Name);
tvDetails.append(details.getProductionYear() == null ? "" : "年份:" + details.getProductionYear() + " ");
tvDetails.append(Genres.equals("") ? "" : "风格:" + Genres + "\n");
tvDetails.append(details.getCommunityRating() == null ? "" : "评分:" + details.getCommunityRating() + " ");
tvDetails.append(details.getOfficialRating() == null ? "" : "评级:" + details.getOfficialRating() + "\n");
if (details.getMediaStreams() != null) {
String video = "";
String audio = "";
String subtitle = "";
for (int i = 0; i < details.getMediaStreams().size(); i++) {
MediaStreams ms = details.getMediaStreams().get(i);
String mstype = ms.getType();
if (mstype.equals("Video")) {
video += ms.getDisplayTitle();
} else if (mstype.equals("Audio")) {
if (ms.getLanguage()!=null && !ms.getLanguage().equals("")) audio += ms.getLanguage() + "";
else audio += ms.getCodec() + "";
} else if (mstype.equals("Subtitle")) {
if (ms.getLanguage() != null && !ms.getLanguage().equals("")) subtitle += ms.getLanguage() + "";
else subtitle += ms.getCodec() + "";
}
}
tvDetails.append(video.equals("") ? "" : "视频:" + video + "\n");
tvDetails.append(audio.equals("") ? "" : "音频:" + audio + "\n");
tvDetails.append(subtitle.equals("") ? "" : "字幕:" + subtitle + "\n");
}
String overview = details.getOverview() == null ? "" : details.getOverview();
tvDetails.append("简介: " + Html.fromHtml(overview));
//填充列表
String type = details.getType();
if (type.equals("Series")) {
fillSeason(ItemId);
} else if (type.equals("Season")) {
String SeriesName = details.getSeriesName() == null ? "" : details.getSeriesName() + "-";
tvTitle.setText(SeriesName + details.getName());
String SeriesId = details.getSeriesId();
fillEpisodes(SeriesId, ItemId);
} else if (type.equals("Episode")) {
String SeriesName = details.getSeriesName() == null ? "" : details.getSeriesName() + "-";
String SeasonName = details.getSeasonName() == null ? "" : details.getSeasonName();
tvTitle.setText(SeriesName + SeasonName);
String SeriesId = details.getSeriesId();
String SeasonId = details.getSeasonId();
fillEpisodes(SeriesId, SeasonId);
} else if (type.equals("Movie")) {
fillMovie(details);
} else if (type.equals("Person")) {
tvDetails.append("\n出生日期" + Utils.UtcToLocal(details.getPremiereDate()) + "\n");
if(null != details.getProductionLocations()) {
tvDetails.append("出生地:" + String.join(",", details.getProductionLocations()));
}
fillItemsByPerson(Id);
}
List<People> Peoples = details.getPeople();
if (Peoples != null) {
if (Peoples.size() > 0) {
fillPeople(Peoples);
}
}
}
private void fillMovie(Item item) {
item.setName("播放: " + item.getName());
List<Item> plist = new ArrayList<>();
plist.add(item);
if (item.getPartCount() > 0) {
JfClient.GetAddPart(item.getId(), new JfClient.JJCallBack() {
@Override
public void onSuccess(Items parts) {
plist.addAll(parts.getItems());
fillItems(plist);
}
}, null);
} else {
fillItems(plist);
}
}
/**
* 填充季
*
* @param SeriesId
*/
private void fillSeason(String SeriesId) {
JfClient.GetSeasons(SeriesId, new JfClient.JJCallBack() {
@Override
public void onSuccess(Items seasons) {
fillItems(seasons.getItems());
}
}, errcb);
}
/**
* 填充集
*
* @param SeriesId
* @param SeasonId
* @return
*/
private void fillEpisodes(String SeriesId, String SeasonId) {
JfClient.GetEpisodes(SeriesId, SeasonId, new JfClient.JJCallBack() {
@Override
public void onSuccess(Items episodes) {
fillItems(episodes.getItems());
}
}, errcb);
}
private void fillItems(List<Item> items) {
JAdapter jAdapter = new JAdapter(items, false);
V7LinearLayoutManager layoutManager = new V7LinearLayoutManager(mGridView.getContext());
layoutManager.setOrientation(V7LinearLayoutManager.HORIZONTAL);
jAdapter.setOnItemClickListener(this);
mGridView.setVisibility(View.VISIBLE);
mGridView.setLayoutManager(layoutManager);
mGridView.setAdapter(jAdapter);
dismissLoadingDialog();
}
/**
* 填充演员表
*
* @param Peoples
*/
private void fillPeople(List<People> Peoples) {
List<Item> Pitems = new ArrayList<>();
for (People p : Peoples) {
Item it = new Item();
it.setId(p.getId());
if (p.getType().equals("Director")) {
it.setName("导演:" + p.getName());
} else if (p.getType().equals("Actor")) {
it.setName("演员:" + p.getName());
} else {
it.setName(p.getName());
}
it.setType(p.getType());
it.setImageTags(new ImageTags());
it.getImageTags().setPrimary(p.getPrimaryImageTag());
Pitems.add(it);
}
tvPeopleLayout.setVisibility(View.VISIBLE);
JAdapter jAdapter = new JAdapter(Pitems, false);
V7LinearLayoutManager layoutManager = new V7LinearLayoutManager(mPeopleGridView.getContext());
layoutManager.setOrientation(V7LinearLayoutManager.HORIZONTAL);
jAdapter.setOnItemClickListener(this);
mPeopleGridView.setLayoutManager(layoutManager);
mPeopleGridView.setAdapter(jAdapter);
}
/**
* 填充演员作品
*
* @param personid
*/
private void fillItemsByPerson(String personid) {
String Term = "&SortBy=DateCreated&SortOrder=Descending&PersonIds=" + personid;
Term += "&IncludeItemTypes=Movie,Series";
JfClient.GetItemsByTerm(Term, new JfClient.JJCallBack() {
@Override
public void onSuccess(Items iitems) {
((TextView) findViewById(R.id.tvListTitle)).setText("演员作品:");
List<Item> items = iitems.getItems();
JAdapter jAdapter = new JAdapter(items, false);
V7GridLayoutManager layoutManager = new V7GridLayoutManager(mGridView.getContext(), 4);
jAdapter.setOnItemClickListener(new JAdapter.OnItemClickListener() {
@Override
public void onClick(Item item) {
String itemId = item.getId();
Intent intent = new Intent(DetailActivity.this, DetailActivity.class);
intent.putExtra("itemId", itemId);
startActivity(intent);
}
});
mGridView.setVisibility(View.VISIBLE);
mGridView.setLayoutManager(layoutManager);
mGridView.setAdapter(jAdapter);
dismissLoadingDialog();
}
}, errcb);
}
@Override
public void onClick(Item item) {
String itemId = item.getId();
String type = item.getType();
Intent intent = null;
if (type.equals("Season")) {
intent = new Intent(this, DetailActivity.class);
intent.putExtra("itemId", itemId);
startActivity(intent);
} else if (type.equals("Episode")) {
JfClient.playList.clear();
JAdapter JA = (JAdapter) mGridView.getAdapter();
List<Item> ja = JA.getData();
if (ja != null) {
for (int i = 0; i < ja.size(); i++) {
Video media = getMedia(ja.get(i));
JfClient.playList.add(media);
if (itemId.equals(media.Id)) {
JfClient.playIndex = i;
}
}
toVlcPlayer();
}
} else if (type.equals("Movie") || type.equals("Video")) {
JfClient.playList.clear();
JfClient.playList.add(getMedia(item));
JfClient.playIndex = 0;
toVlcPlayer();
} else if (type.equals("Actor") || type.equals("Director")) {
intent = new Intent(this, DetailActivity.class);
intent.putExtra("itemId", itemId);
startActivity(intent);
}
}
/**
* 组合播放媒体
*
* @param item
* @return
*/
public Video getMedia(Item item) {
Video media = new Video();
media.Id = item.getId();
media.Name = item.getName();
media.cover = "";
media.Url = JfClient.GetPlayUrl(media.Id);
if (item.getUserData() != null) {
UserData userdata = item.getUserData();
media.startPositionTicks = userdata.getPlaybackPositionTicks();
}
return media;
}
public void toVlcPlayer() {
Intent intent;
if(JfClient.config.isExtensionPlayer()){
String videourl = JfClient.playList.get(JfClient.playIndex).Url;
Uri uri = Uri.parse(videourl);
intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"video/mp4");
}else{
intent = new Intent(this, VlcPlayerActivity.class);
}
this.startActivity(intent);
}
private JfClient.JJCallBack errcb = new JfClient.JJCallBack() {
@Override
public void onError(String str) {
ShowToask(str);
dismissLoadingDialog();
}
};
}

View File

@ -0,0 +1,235 @@
package org.sifacai.vlcjellyfin;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.owen.tvrecyclerview.widget.V7LinearLayoutManager;
import org.sifacai.vlcjellyfin.Bean.Item;
import org.sifacai.vlcjellyfin.Bean.Items;
import java.util.List;
public class HomeActivity extends BaseActivity {
private final String TAG = "HomeActivity";
private LinearLayout tvContiner;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.jellyfin_home);
disableActiveBarBack();
tvContiner = findViewById(R.id.tvItems);
JfClient.init(getApplication());
}
private JfClient.JJCallBack connErr = new JfClient.JJCallBack() {
@Override
public void onError(String str) {
dismissLoadingDialog();
ShowToask(str);
showLoginDialog();
}
};
@Override
protected void onResume() {
super.onResume();
initData();
}
/**
* 登录框
*/
private void showLoginDialog() {
Log.d(TAG, "showLoginDialog: 跳出登录框");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
AlertDialog alert = builder.setTitle("登录")
.setMessage("请输入登录信息")
.setView(R.layout.dialog_login)
.show();
alert.setCanceledOnTouchOutside(false);
EditText urlbox = alert.findViewById(R.id.dialog_login_url);
EditText unbox = alert.findViewById(R.id.dialog_login_un);
EditText pwbox = alert.findViewById(R.id.dialog_login_pw);
CheckBox saveBox = alert.findViewById(R.id.dialog_login_save);
TextView submit = alert.findViewById(R.id.dialog_login_submit);
TextView cancel = alert.findViewById(R.id.dialog_login_cancel);
urlbox.setText(JfClient.config.getJellyfinUrl());
unbox.setText(JfClient.config.getUserName());
pwbox.setText(JfClient.config.getPassWord());
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.exit(0);
}
});
submit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showLoadingDialog("正在验证服务器地址!");
JfClient.VerityServerUrl(urlbox.getText().toString(), new JfClient.JJCallBack() {
@Override
public void onSuccess(Boolean bool) {
setLoadingText("正在验证用户名和密码!");
JfClient.AuthenticateByName(unbox.getText().toString(), pwbox.getText().toString(), new JfClient.JJCallBack() {
@Override
public void onSuccess(Boolean bool) {
dismissLoadingDialog();
alert.dismiss();
initView(); //加载首页
}
}, new JfClient.JJCallBack(){
@Override
public void onError(String str) {
dismissLoadingDialog();
ShowToask("用户名密码验证失败!");
}
}, saveBox.isChecked());
}
}, new JfClient.JJCallBack() {
@Override
public void onError(String str) {
dismissLoadingDialog();
ShowToask("服务器地址不正确!");
}
});
}
});
}
private void initData(){
if (JfClient.AccessToken.equals("") || JfClient.UserId.equals("")) {
showLoadingDialog("正在验证服务器地址!");
JfClient.VerityServerUrl(JfClient.config.getJellyfinUrl(), new JfClient.JJCallBack() {
@Override
public void onSuccess(Boolean bool) {
setLoadingText("正在验证用户名和密码!");
JfClient.AuthenticateByName(JfClient.config.getUserName(), JfClient.config.getPassWord(), new JfClient.JJCallBack() {
@Override
public void onSuccess(Boolean bool) {
dismissLoadingDialog();
initView();
}
}, connErr, false);
}
}, connErr);
}else{
Log.d(TAG, "initData: 跳出");
initView();
}
}
private void initView() {
showLoadingDialog("正在加载首页…………");
tvContiner.removeAllViews();
JfClient.GetViews(new JfClient.JJCallBack() {
@Override
public void onSuccess(Items views) {
List<Item> items = views.getItems();
addRowTvRecyclerView("我的媒体", items, true);
for (int i = 0; i < items.size(); i++) {
Item item = items.get(i);
JfClient.GetLatest(item.getId(), new JfClient.JJCallBack() {
@Override
public void onSuccess(Items latests) {
addRowTvRecyclerView("新的 " + item.getName(), latests.getItems(), false);
}
}, errcb);
}
dismissLoadingDialog();
}
}, errcb);
JfClient.GetResume(new JfClient.JJCallBack() {
@Override
public void onSuccess(Items resumes) {
for(Item it : resumes.getItems()){
String SeriesName = it.getSeriesName() == null ? "" : it.getSeriesName() + "-";
String SeasonName = it.getSeasonName() == null ? "" : it.getSeasonName() + "-";
it.setName(SeriesName+SeasonName+it.getName());
}
addRowTvRecyclerView("最近播放", resumes.getItems(), false);
}
}, errcb);
}
/**
* 添加类别行
*/
private void addRowTvRecyclerView(String title, List<Item> items, boolean horizon) {
JRecyclerView tvRecyclerView = (JRecyclerView) LayoutInflater.from(this)
.inflate(R.layout.home_horizon_tvrecycler, null);
((V7LinearLayoutManager) tvRecyclerView.getLayoutManager()).setOrientation(V7LinearLayoutManager.HORIZONTAL);
JAdapter jAdapter = new JAdapter(items, horizon);
jAdapter.setOnItemClickListener(new JAdapter.OnItemClickListener() {
@Override
public void onClick(Item item) {
String type = item.getType();
String itemId = item.getId();
Intent intent = null;
if (type.equals("Folder") || type.equals("CollectionFolder")) {
intent = new Intent(mAA, CollectionActivity.class);
} else {
intent = new Intent(mAA, DetailActivity.class);
}
intent.putExtra("itemId", itemId);
mAA.startActivity(intent);
}
});
tvRecyclerView.setAdapter(jAdapter);
TextView titleView = new TextView(tvRecyclerView.getContext());
titleView.setText(title);
tvContiner.addView(titleView);
tvContiner.addView(tvRecyclerView);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
exit();
return false;
}
return super.onKeyDown(keyCode, event);
}
private long exitTime = 0;
public void exit() {
if ((System.currentTimeMillis() - exitTime) > 2000) {
Toast.makeText(getApplicationContext(), "再按一次退出程序",
Toast.LENGTH_SHORT).show();
exitTime = System.currentTimeMillis();
} else {
finish();
System.exit(0);
}
}
public JfClient.JJCallBack errcb = new JfClient.JJCallBack(){
@Override
public void onError(String str) {
dismissLoadingDialog();
ShowToask(str);
}
};
}

View File

@ -0,0 +1,147 @@
package org.sifacai.vlcjellyfin;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.owen.tvrecyclerview.widget.TvRecyclerView;
import com.squareup.picasso.Picasso;
import org.sifacai.vlcjellyfin.Bean.Item;
import org.sifacai.vlcjellyfin.Bean.Items;
import org.sifacai.vlcjellyfin.Bean.People;
import java.util.ArrayList;
import java.util.List;
public class JAdapter extends RecyclerView.Adapter {
private String TAG = "JAdapter:";
private List<Item> items;
private boolean horizon;
class VH extends RecyclerView.ViewHolder{
public String id;
public String type;
public TextView tvName;
public ImageView tvCover;
public SeekBar tvPlayedPercentage;
public VH(View v) {
super(v);
id = "";
type = "";
tvName = v.findViewById(R.id.tvName);
tvCover = v.findViewById(R.id.ivThumb);
tvPlayedPercentage = v.findViewById(R.id.tvPlayedPercentage);
}
}
public JAdapter(List<Item> items) {
this.items = items;
this.horizon = false;
}
public JAdapter(List<Item> items,Boolean horizon) {
this.items = items;
this.horizon = horizon;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = null;
if(horizon){
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_h,parent,false);
}else{
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_v,parent,false);
}
return new VH(v);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
VH v = (VH)holder;
Item item = items.get(position);
v.id = item.getId();
v.tvName.setText(item.getName());
if(item.getUserData() != null){
int pp = (int) Math.round(item.getUserData().getPlayedPercentage());
v.tvPlayedPercentage.setProgress(pp);
v.tvPlayedPercentage.setVisibility(View.VISIBLE);
}
v.type = item.getType();
String imgUrl = JfClient.GetImgUrl(item.getId(),item.getImageTags().getPrimary());
if (!TextUtils.isEmpty(imgUrl)) {
Picasso.get()
.load(imgUrl)
.placeholder(R.drawable.img_loading_placeholder)
.error(R.drawable.img_loading_placeholder)
.into(v.tvCover);
} else {
v.tvCover.setImageResource(R.drawable.img_loading_placeholder);
}
v.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
listener.onClick(item);
}
});
}
public List<Item> getData(){
return items;
}
@Override
public int getItemCount() {
int count = items.size();
return count;
}
/**
* 设置数据
* @param items
*/
public void setItems(List<Item> items){
this.items = items;
notifyDataSetChanged();
}
/**
* 添加数据
* @param items
*/
public void addItems(List<Item> items){
int c = this.items.size();
this.items.addAll(items);
notifyItemRangeInserted(c,items.size());
}
public void clearItems(){
this.items = new ArrayList<>();
notifyDataSetChanged();
}
//定义OnItemClickListener接口
public interface OnItemClickListener {
void onClick(Item item);
}
public OnItemClickListener listener;
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}

View File

@ -0,0 +1,44 @@
package org.sifacai.vlcjellyfin;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.BounceInterpolator;
import com.owen.tvrecyclerview.widget.TvRecyclerView;
public class JRecyclerView extends TvRecyclerView {
public JRecyclerView(Context context) {
super(context);
init(context);
}
public JRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public JRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context){
setOnItemListener(new TvRecyclerView.OnItemListener() {
@Override
public void onItemPreSelected(TvRecyclerView parent, View itemView, int position) {
itemView.animate().scaleX(1.0f).scaleY(1.0f).setDuration(300).setInterpolator(new BounceInterpolator()).start();
}
@Override
public void onItemSelected(TvRecyclerView parent, View itemView, int position) {
itemView.animate().scaleX(1.05f).scaleY(1.05f).setDuration(300).setInterpolator(new BounceInterpolator()).start();
}
@Override
public void onItemClick(TvRecyclerView parent, View itemView, int position) {}
});
}
}

View File

@ -0,0 +1,713 @@
package org.sifacai.vlcjellyfin;
import android.app.Application;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.AbsCallback;
import com.lzy.okgo.model.HttpHeaders;
import com.lzy.okgo.model.Response;
import com.squareup.picasso.OkHttp3Downloader;
import com.squareup.picasso.Picasso;
import org.sifacai.vlcjellyfin.Bean.Item;
import org.sifacai.vlcjellyfin.Bean.Items;
import java.io.IOException;
import java.lang.reflect.Type;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient;
public class JfClient {
public static final String TAG = "JellyfinClient";
public static final String XEmbyAuthorization = "MediaBrowser Client=\"Vlc_J_TV\", Device=\"Vlc_J_TV\", DeviceId=\"TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xOyBXa\", Version=\"10.8.1\"";
public static HttpHeaders headers;
public static Config config;
public static String UserId = "";
public static String AccessToken = "";
public static int playIndex = 0; //当前播放
public static ArrayList<Video> playList = new ArrayList<>(); //播放列表
public enum ReportType {
playing,
Progress,
stop
}
/**
* description 忽略https证书验证
*
* @author yanzy
* @version 1.0
* @date 2021/9/8 14:42
*/
private static TrustManager[] getTrustManager() {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
return trustAllCerts;
}
/**
* description 忽略https证书验证
*`在这里插入代码片`
* @author yanzy
* @version 1.0
* @date 2021/9/8 14:42
*/
public static SSLSocketFactory getSSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, getTrustManager(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* description 忽略https证书验证
*
* @author yanzy
* @version 1.0
* @date 2021/9/8 14:42
*/
public static HostnameVerifier getHostnameVerifier() {
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
};
return hostnameVerifier;
}
/**
* 初始化配置
*
* @param application
*/
public static void init(Application application) {
config = new Config(application);
SetHeaders();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(getSSLSocketFactory());
builder.hostnameVerifier(getHostnameVerifier());
builder.connectTimeout(5, TimeUnit.SECONDS);
builder.readTimeout(3, TimeUnit.SECONDS);
builder.writeTimeout(5, TimeUnit.SECONDS);
OkGo.getInstance().init(application)
.setOkHttpClient(builder.build())
.setRetryCount(3);
Picasso.setSingletonInstance(new Picasso.Builder(application.getBaseContext())
.downloader(new OkHttp3Downloader(builder.build()))
.build()
);
}
/**
* 回放报告
*
* @param type
* @param id
* @param PositionTicks
*/
public static void ReportPlayBackState(ReportType type, String id, long PositionTicks) {
String url = config.getJellyfinUrl();
if (type == ReportType.playing) {
url += "/Sessions/Playing";
} else if (type == ReportType.Progress) {
url += "/Sessions/Playing/Progress";
} else if (type == ReportType.stop) {
url += "/Sessions/Playing/Stopped";
}
String reqstr = "{\"itemId\":\"" + id + "\",\"PositionTicks\":\"" + PositionTicks * 10000 + "\"}";
SendPost(url, reqstr, new JJCallBack() {
@Override
public void onSuccess(String str) {
//回放报告
}
}, null);
}
/**
* 获取封面图url
*
* @param itemid
* @param tagid
* @return
*/
public static String GetImgUrl(String itemid, String tagid) {
String url = config.getJellyfinUrl() + "/Items/" + itemid + "/Images/Primary";
url += "?fillHeight=286&fillWidth=200&quality=96&tag=" + tagid;
return url;
}
public static String GetImgUrl(Item item) {
if (item.getImageTags() == null) return "";
if (item.getImageTags().getPrimary() == null || item.getImageTags().getPrimary().equals(""))
return "";
return GetImgUrl(item.getId(), item.getImageTags().getPrimary());
}
/**
* 获取播放url
*
* @param itemid
* @return
*/
public static String GetPlayUrl(String itemid) {
String playurl = config.getJellyfinUrl() + "/videos/" + itemid + "/stream.mp4?static=true";
return playurl;
}
public static void SearchByTerm(String term, int limit, JJCallBack scb, JJCallBack errcb) {
String BaseUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items?";
BaseUrl += "Fields=PrimaryImageAspectRatio,CanDelete,BasicSyncInfo,MediaSourceCount";
BaseUrl += "&Recursive=true&EnableTotalRecordCount=false&ImageTypeLimit=1&IncludePeople=false";
BaseUrl += "&IncludeMedia=true&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false";
BaseUrl += "&Limit=" + limit;
String PersonUrl = config.getJellyfinUrl() + "/Persons?Fields=PrimaryImageAspectRatio%2CCanDelete%2CBasicSyncInfo%2CMediaSourceCount&Recursive=true";
PersonUrl += "&EnableTotalRecordCount=false&ImageTypeLimit=1&IncludePeople=true&IncludeMedia=false";
PersonUrl += "&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false&userId=" + UserId;
String personUrl = PersonUrl + "&searchTerm=" + term + "&Limit=" + limit;
String movieUrl = BaseUrl + "&searchTerm=" + term + "&IncludeItemTypes=Movie";
String seriesUrl = BaseUrl + "&searchTerm=" + term + "&IncludeItemTypes=Series";
String episodeUrl = BaseUrl + "&searchTerm=" + term + "&IncludeItemTypes=Episode";
JsonArray items = new JsonArray();
new Thread(
new Runnable() {
@Override
public void run() {
try {
Items items = new Items();
items.setItems(new ArrayList<>());
String jsonstr = SendGet(movieUrl);
Items moviejob = Utils.jsonToClass(jsonstr, Items.class);
if (moviejob != null) {
items.AddItems(moviejob.getItems());
items.setTotalRecordCount(items.getTotalRecordCount() + moviejob.getTotalRecordCount());
}
jsonstr = SendGet(seriesUrl);
Items seriesobj = Utils.jsonToClass(jsonstr, Items.class);
if (seriesobj != null) {
items.AddItems(seriesobj.getItems());
items.setTotalRecordCount(items.getTotalRecordCount() + seriesobj.getTotalRecordCount());
}
jsonstr = SendGet(personUrl);
Items personobj = Utils.jsonToClass(jsonstr, Items.class);
if (personobj != null) {
items.AddItems(personobj.getItems());
items.setTotalRecordCount(items.getTotalRecordCount() + personobj.getTotalRecordCount());
}
scb.onSuccess(items);
} catch (Exception e) {
errcb.onError(e.toString());
}
}
}
).start();
}
/**
* 获取项目附加部分
*
* @param itemid
* @param cb
*/
public static void GetAddPart(String itemid, JJCallBack cb, JJCallBack errcb) {
String AddPartUrl = config.getJellyfinUrl() + "/Videos/" + itemid + "/AdditionalParts?userId=" + UserId;
SendGet(AddPartUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Items items = Utils.jsonToClass(str, Items.class);
cb.onSuccess(items);
} catch (Exception e) {
errcb.onError(e.getMessage());
}
}
}, errcb);
}
/**
* 获取剧集
*
* @param seriesId 剧ID
* @param seasonId 季ID
* @param cb
*/
public static void GetEpisodes(String seriesId, String seasonId, JJCallBack cb, JJCallBack errcb) {
String EpisodesUrl = config.getJellyfinUrl() + "/Shows/" + seriesId + "/Episodes?seasonId=" + seasonId;
EpisodesUrl += "&userId=" + UserId;
EpisodesUrl += "&Fields=ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount,Overview";
SendGet(EpisodesUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Items items = Utils.jsonToClass(str, Items.class);
cb.onSuccess(items);
} catch (Exception e) {
errcb.onError(e.getMessage());
}
}
}, errcb);
}
/**
* 获取季(Seasons)数据
*
* @param seriesId 剧ID
* @param cb
*/
public static void GetSeasons(String seriesId, JJCallBack cb, JJCallBack errcb) {
String SeasonsUrl = config.getJellyfinUrl() + "/Shows/" + seriesId + "/Seasons?userId=" + UserId;
SeasonsUrl += "&Fields=ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,MediaSourceCount";
SendGet(SeasonsUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Items items = Utils.jsonToClass(str, Items.class);
cb.onSuccess(items);
} catch (Exception e) {
errcb.onError(e.getMessage());
}
}
}, errcb);
}
/**
* 根据条件获取条目列表
*
* @param term 可以是 &Limit&SortBy&IncludeItemTypes&PersonIds
* @param scb
* @param errcb
*/
public static void GetItemsByTerm(String term, JJCallBack scb, JJCallBack errcb) {
String BaseUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items?Recursive=true&StartIndex=0&CollapseBoxSetItems=false";
BaseUrl += term;
SendGet(BaseUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Items items = Utils.jsonToClass(str, Items.class);
scb.onSuccess(items);
} catch (Exception e) {
errcb.onError(e.getMessage());
}
}
}, errcb);
}
/**
* 获取合集条目
*
* @param parentId
* @param type 类型 movietvshows
* @param sortBy 排序条件 评分加入时间
* @param sortOrder 排序规则 升序降序
* @param limit 每页条数
* @param page
* @param cb
*/
public static void GetCollection(String parentId, String type, String sortBy, String sortOrder, int limit, int page, JJCallBack cb, JJCallBack errcb) {
String itemsUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items?ParentId=" + parentId + "&Limit=" + limit;
itemsUrl += "&Recursive=true&Fields=PrimaryImageAspectRatio,BasicSyncInfo,Seasons,Episodes&ImageTypeLimit=1";
itemsUrl += "&EnableImageTypes=Primary,Backdrop,Banner,Thumb";
itemsUrl += "&SortBy=" + sortBy + ",SortName,ProductionYear&SortOrder=" + sortOrder;
if (type.equals("tvshows")) {
itemsUrl += "&IncludeItemTypes=Series";
} else if (type.equals("movies")) {
itemsUrl += "&IncludeItemTypes=Movie";
} else {
itemsUrl += "&IncludeItemTypes=Movie,Series";
}
int startIndex = page * limit - limit;
itemsUrl += "&StartIndex=" + startIndex;
SendGet(itemsUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Items items = Utils.jsonToClass(str, Items.class);
cb.onSuccess(items);
} catch (Exception e) {
errcb.onError(e.getMessage());
}
}
}, errcb);
}
/**
* 获取最新项目
*
* @param parentId
* @param cb
*/
public static void GetLatest(String parentId, JJCallBack cb, JJCallBack errcb) {
String lastestUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items/Latest?";
lastestUrl += "Limit=16&Fields=PrimaryImageAspectRatio%2CBasicSyncInfo%2CPath";
lastestUrl += "&ImageTypeLimit=1&EnableImageTypes=Primary,Backdrop,Thumb";
lastestUrl += "&ParentId=" + parentId;
SendGet(lastestUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
Type type = new TypeToken<List<Item>>() {
}.getType();
List<Item> rets = Utils.jsonToClass(str, type);
if (null != rets) {
Items items = new Items();
items.setItems(rets);
items.setTotalRecordCount(rets.size());
cb.onSuccess(items);
}
}
}, errcb);
}
/**
* 获取最近播放
*
* @param cb
*/
public static void GetResume(JJCallBack cb, JJCallBack err) {
String resumeUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items/Resume?";
resumeUrl += "Limit=12&Recursive=true&Fields=PrimaryImageAspectRatio,BasicSyncInfo";
resumeUrl += "&ImageTypeLimit=1&EnableImageTypes=Primary,Backdrop,Thumb";
resumeUrl += "&EnableTotalRecordCount=false&MediaTypes=Video";
SendGet(resumeUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Items items = Utils.jsonToClass(str, Items.class);
cb.onSuccess(items);
} catch (Exception e) {
err.onError(e.getMessage());
}
}
}, err);
}
/**
* 获取首页
*
* @param cb
*/
public static void GetViews(JJCallBack cb, JJCallBack err) {
String viewsUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Views";
SendGet(viewsUrl, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Items views = Utils.jsonToClass(str, Items.class);
cb.onSuccess(views);
} catch (Exception e) {
err.onError(e.getMessage());
}
}
}, err);
}
/**
* 获取项目详情
*
* @param itemid
* @param cb
*/
public static void GetItemInfo(String itemid, JJCallBack cb, JJCallBack err) {
String url = config.getJellyfinUrl() + "/Users/" + UserId + "/Items/" + itemid;
SendGet(url, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
Item item = Utils.jsonToClass(str, Item.class);
cb.onSuccess(item);
} catch (Exception e) {
err.onError(e.getMessage());
}
}
}, err);
}
/**
* 验证用户名密码
*
* @param username
* @param password
* @param cb
*/
public static void AuthenticateByName(String username, String password, JJCallBack cb, JJCallBack err, boolean saveUser) {
if(username.equals("") || password.equals("")){
err.onError("用户名和密码验证失败!");
return;
}
String url = config.getJellyfinUrl() + "/Users/authenticatebyname";
String reqjson = "{\"Username\":\"" + username + "\",\"Pw\":\"" + password + "\"}";
SendPost(url, reqjson, new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
JsonObject userObj = new Gson().fromJson(str, JsonObject.class);
if (userObj.has("User") && userObj.has("AccessToken")) {
String userId = userObj.get("User").getAsJsonObject().get("Id").getAsString();
String Token = userObj.get("AccessToken").getAsString();
if (!Token.equals("")) {
UserId = userId;
AccessToken = Token;
if (saveUser) {
config.setUserName(username);
config.setPassWord(password);
}
SetHeaders();
cb.onSuccess(true);
} else {
err.onError("验证失败:" + str);
}
} else {
err.onError("验证失败:" + str);
}
} catch (Exception e) {
err.onError("验证失败:" + e.getMessage());
}
}
}, err);
}
/**
* 获取用户列表
*
* @param cb
*/
public static void GetUsers(JJCallBack cb, JJCallBack err) {
String url = config.getJellyfinUrl() + "/users/public";
SendGet(url, new JJCallBack() {
@Override
public void onSuccess(String str) {
JsonArray users = new Gson().fromJson(str, JsonArray.class);
cb.onSuccess(users);
}
}, err);
}
/**
* 验证服务器地址
*
* @param url
* @param cb
*/
public static void VerityServerUrl(String url, JJCallBack cb, JJCallBack err) {
if (url.startsWith("http://") || url.startsWith("https://")) {
SendGet(url + "/system/info/public", new JJCallBack() {
@Override
public void onSuccess(String str) {
try {
JsonObject serverInfo = new Gson().fromJson(str, JsonObject.class);
String ServerId = "";
ServerId = serverInfo.get("Id").getAsString();
if (ServerId == null || ServerId.length() == 0) {
err.onError("服务器连接失败!");
} else {
config.setJellyfinUrl(url);
cb.onSuccess(true);
}
} catch (Exception e) {
err.onError(e.getMessage());
}
}
}, err);
} else {
err.onError("服务器地址不正确!");
}
}
/**
* 设置请求头
*/
public static void SetHeaders() {
headers = new HttpHeaders();
String xea = XEmbyAuthorization;
if (null != AccessToken && AccessToken.length() > 0) {
xea += ", Token=\"" + AccessToken + "\"";
}
headers.put("Accept", "application/json");
headers.put("Accept-Language", "zh-CN,zh;q=0.9");
headers.put("X-Emby-Authorization", xea);
}
/**
* Get
*
* @param url
* @param cb
*/
public static void SendGet(String url, JJCallBack cb, JJCallBack errcb) {
OkGo.<String>get(url).headers(headers).execute(new AbsCallback<String>() {
@Override
public String convertResponse(okhttp3.Response response) throws IOException {
String result = "";
if (null != response.body()) {
result = response.body().string();
}
return result;
}
@Override
public void onSuccess(Response<String> response) {
cb.onSuccess(response.body());
}
@Override
public void onError(Response<String> response) {
if (errcb != null) {
errcb.onError(response.toString());
}
}
});
}
/**
* Post
*
* @param url
* @param jsonStr
* @param cb
*/
public static void SendPost(String url, String jsonStr, JJCallBack cb, JJCallBack errcb) {
OkGo.<String>post(url).headers(headers).upJson(jsonStr).execute(new AbsCallback<String>() {
@Override
public String convertResponse(okhttp3.Response response) throws Throwable {
String result = "";
if (null != response.body()) {
result = response.body().string();
}
return result;
}
@Override
public void onSuccess(Response<String> response) {
cb.onSuccess(response.body());
}
@Override
public void onError(Response<String> response) {
if (errcb != null) {
errcb.onError(response.body());
}
}
});
}
/**
* 阻塞式Get
*
* @param url
*/
public static String SendGet(String url) throws IOException {
String response = "";
response = OkGo.<String>get(url).headers(headers).execute().body().string();
return response;
}
public static class JJCallBack implements JCallBack {
@Override
public void onSuccess(String str) {
}
@Override
public void onSuccess(Boolean bool) {
}
@Override
public void onSuccess(JsonObject jsonObject) {
}
@Override
public void onSuccess(JsonArray jsonArray) {
}
@Override
public void onSuccess(Item item) {
}
@Override
public void onSuccess(Items items) {
}
@Override
public void onError(String str) {
}
}
public interface JCallBack {
void onSuccess(String str);
void onSuccess(Boolean bool);
void onSuccess(JsonObject jsonObject);
void onSuccess(JsonArray jsonArray);
void onSuccess(Item item);
void onSuccess(Items items);
void onError(String str);
}
}

View File

@ -0,0 +1,106 @@
package org.sifacai.vlcjellyfin;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ScrollView;
import android.widget.TextView;
import java.util.ArrayList;
public class PopMenu extends PopupWindow {
public class menu{
public int groupid;
public int id;
public int orderid;
public String name;
public View v;
}
private Context context;
private ArrayList<menu> items;
private View attView;
private ScrollView contentView;
private LinearLayout itemContiner;
public PopMenu(Context context, View attView) {
super(context);
this.context = context;
items = new ArrayList<>();
setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
setOutsideTouchable(true);
setFocusable(true);
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
contentView = (ScrollView) LayoutInflater.from(context).inflate(R.layout.popmenu,null);
setContentView(contentView);
itemContiner = contentView.findViewById(R.id.popitemContiner);
this.attView = attView;
setClippingEnabled(true);
}
public menu add(int groupid,int id,int orderid,String name){
menu m = new menu();
m.groupid = groupid;
m.id = id;
m.orderid = orderid;
m.name = name;
View v = LayoutInflater.from(context).inflate(R.layout.popmenu_item,null);
m.v = v;
((TextView)v).setText(name);
items.add(m);
itemContiner.addView(v);
return m;
}
public void show(){
if(items.size() > 12){
setHeight(500);
}
contentView.measure(makeDropDownMeasureSpec(getWidth())
,makeDropDownMeasureSpec(getHeight()));
int offx = 0;
int offy = contentView.getMeasuredHeight() + attView.getHeight() + 2;
showAsDropDown(attView,offx,-offy);
}
public void show(int index){
show();
// if(index >=0 && index < items.size()) {
// items.get(index).v.requestFocus();
// }
for (menu m:items) {
if(m.id == index){
m.v.requestFocus();
}
}
}
public void show(String name){
show();
for (menu m:items) {
if(m.name.equals(name)){
m.v.requestFocus();
}
}
}
@SuppressWarnings("ResourceType")
private static int makeDropDownMeasureSpec(int measureSpec) {
int mode;
if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) {
mode = View.MeasureSpec.UNSPECIFIED;
} else {
mode = View.MeasureSpec.EXACTLY;
}
return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode);
}
}

View File

@ -0,0 +1,128 @@
package org.sifacai.vlcjellyfin;
import android.content.Intent;
import android.os.Bundle;
import android.telecom.Call;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.owen.tvrecyclerview.widget.TvRecyclerView;
import com.owen.tvrecyclerview.widget.V7GridLayoutManager;
import org.sifacai.vlcjellyfin.Bean.Item;
import org.sifacai.vlcjellyfin.Bean.Items;
import java.util.ArrayList;
import java.util.List;
public class SearchActivity extends BaseActivity implements JAdapter.OnItemClickListener {
private TvRecyclerView mGridView;
private JAdapter adapter;
private final int limit = 24;
private String BaseUrl;
private EditText searchTermEdit;
private ImageView searchBtn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
if (JfClient.UserId.equals("") || JfClient.AccessToken.equals("")) {
finish();
}
ActionBar actionBar = getSupportActionBar();
if (null != actionBar) {
ImageView acb = actionBar.getCustomView().findViewById(R.id.actionBar_searchBtn);
if (null != acb) {
acb.setVisibility(View.GONE);
}
}
mGridView = findViewById(R.id.mGridView);
V7GridLayoutManager v7GridLayoutManager = new V7GridLayoutManager(this, 6);
mGridView.setLayoutManager(v7GridLayoutManager);
mGridView.setItemAnimator(null); //防崩溃
adapter = new JAdapter(new ArrayList<>());
adapter.setOnItemClickListener(this);
mGridView.setAdapter(adapter);
searchTermEdit = findViewById(R.id.searchTermEdit);
searchBtn = findViewById(R.id.searchBtn);
searchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String st = searchTermEdit.getText().toString().trim();
if (st.length() > 0) {
adapter.clearItems();
Search(st);
}
}
});
searchTermEdit.requestFocus();
}
private void Search(String searchTerm) {
showLoadingDialog("搜索中………………");
JfClient.SearchByTerm(searchTerm,16,new JfClient.JJCallBack(){
@Override
public void onSuccess(Items items) {
mAA.runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.addItems(items.getItems());
}
});
dismissLoadingDialog();
}
},new JfClient.JJCallBack(){
@Override
public void onError(String str) {
ShowToask("搜索时发生错误:" + str);
dismissLoadingDialog();
}
});
}
private void fillItems(List<Item> items) {
mAA.runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.addItems(items);
}
});
}
@Override
public void onClick(Item item) {
String itemId = item.getId();
String type = item.getType();
Intent intent = null;
switch (type) {
case "Series":
case "Season":
case "Episode":
case "Movie":
case "Video":
case "Person":
intent = new Intent(this, DetailActivity.class);
intent.putExtra("itemId", itemId);
this.startActivity(intent);
break;
default:
ShowToask("未知类型!");
}
}
}

View File

@ -0,0 +1,110 @@
package org.sifacai.vlcjellyfin;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.AbsCallback;
import com.lzy.okgo.callback.Callback;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.model.HttpHeaders;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;
import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class Utils {
/**
* 标准时间转换
*
* @param utcTime
* @return
*/
public static String UtcToLocal(String utcTime) {
if(utcTime == null || utcTime.length() == 0) return "";
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
String dt = "";
try {
Date date = df.parse(utcTime);
SimpleDateFormat sdt = new SimpleDateFormat("yyyy-MM-dd");
dt = sdt.format(date);
} catch (ParseException e) {
e.printStackTrace();
}
return dt;
}
public static int getPixelsFromDp(Activity context, int i) {
DisplayMetrics metrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(metrics);
return (i * metrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
}
public static <T> T jsonToClass(String jsonstr, Type tClass) {
if (jsonstr != null && jsonstr.length() > 0) {
//Gson gson = new GsonBuilder().registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory()).create();
Gson gson = new Gson();
return gson.fromJson(jsonstr, tClass);
}
return null;
}
public static class NullStringToEmptyAdapterFactory<T> implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (rawType != String.class) {
return null;
}
return (TypeAdapter<T>) new StringNullAdapter();
}
}
public static class StringNullAdapter extends TypeAdapter<String> {
@Override
public String read(JsonReader reader) throws IOException {
// TODO Auto-generated method stub
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";
}
return reader.nextString();
}
@Override
public void write(JsonWriter writer, String value) throws IOException {
// TODO Auto-generated method stub
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
}
}
}

View File

@ -0,0 +1,12 @@
package org.sifacai.vlcjellyfin;
import android.net.Uri;
public class Video {
public String Id = "";
public String Url = null;
public String cover = "";
public String Name = "";
public long startPositionTicks = 0L;
public long PositionTicks = 0L;
}

View File

@ -0,0 +1,619 @@
package org.sifacai.vlcjellyfin;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import org.videolan.libvlc.Dialog;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.util.VLCVideoLayout;
import java.util.ArrayList;
import java.util.List;
public class VlcPlayerActivity extends BaseActivity implements MediaPlayer.EventListener
, View.OnClickListener {
private static final String TAG = "VLC播放器";
public final int Type_SubtitleTrack = 0;
public final int Type_AudioTrack = 1;
public final int Type_Playlist = 2;
public final int Type_Scale = 3;
public final int Type_Speed = 4;
private Activity mActivity;
private MediaPlayer mediaPlayer;
private LibVLC libVLC;
private VLCVideoLayout vlcVideoLayout;
private FrameLayout Controller; //总控
private LinearLayout ControllerTop;
private LinearLayout ControllerBottom;
private LinearLayout ControllerBottomTop;
private LinearLayout ControllerBottomBottom;
private TextView videoTitle;
private TextView currTime;
private TextView countTime;
private TextView speedBtn;
private TextView scaleBtn;
private ImageView playPauseBtn;
private ImageView stopBtn;
private ImageView subTracksBtn;
private ImageView audioTracksBtn;
private ImageView playListBtn;
private ImageView pauseFlag;
private SeekBar currPostion;
private ProgressBar loading;
private PopMenu playListMenu = null; //播放列表
private PopMenu subTrackMenu = null; //字幕菜单
private PopMenu audioTrackMenu = null; //单轨菜单
private PopMenu scaleTypeMenu = null; //缩放菜单
private PopMenu speedMenu = null; //播放速率菜单
private float speedRate[] = {0.5f, 1.0f, 1.5f, 2.0f}; //倍速播放列表
private int updateTimeCount = 0;
private int ReportCount = 30;
private Video currItem; //当前播放项目
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vlc_player);
mActivity = this;
initVlc();
initController();
play();
}
/**
* 初始化播放器
*/
private void initVlc() {
List<String> vlcoptions = new ArrayList<>();
vlcoptions.add("-v");
vlcVideoLayout = findViewById(R.id.VideoView);
libVLC = new LibVLC(this,vlcoptions);
Dialog.setCallbacks(libVLC,callbacks);
mediaPlayer = new MediaPlayer(libVLC);
mediaPlayer.attachViews(vlcVideoLayout, null, true, false);
mediaPlayer.setEventListener(this);
}
Dialog.Callbacks callbacks = new Dialog.Callbacks() {
@Override
public void onDisplay(Dialog.ErrorMessage dialog) {
}
@Override
public void onDisplay(Dialog.LoginDialog dialog) {
}
@Override
public void onDisplay(Dialog.QuestionDialog dialog) {
if(dialog.getQuestionType() == 1){
dialog.postAction(1);
}else {
Log.d(TAG, "onDisplay_QuestionDialog: " + dialog.getQuestionType() + " : " + dialog.getTitle() + " : " + dialog.getText());
}
}
@Override
public void onDisplay(Dialog.ProgressDialog dialog) {
}
@Override
public void onCanceled(Dialog dialog) {
}
@Override
public void onProgressUpdate(Dialog.ProgressDialog dialog) {
}
};
@Override
public void onEvent(MediaPlayer.Event event) {
switch (event.type) {
case MediaPlayer.Event.Playing: //媒体打开成功
Hide();
pauseFlag.setVisibility(View.GONE);
Log.d(TAG, "onEvent: Playing");
ReportPlayState(JfClient.ReportType.playing);
initMenu();
break;
case MediaPlayer.Event.Paused: //暂停
pauseFlag.setVisibility(View.VISIBLE);
break;
case MediaPlayer.Event.Stopped:
Log.d(TAG, "onEvent: Stopped");
ReportPlayState(JfClient.ReportType.stop);
playNext();
break;
case MediaPlayer.Event.Opening: //媒体打开
Log.d(TAG, "onEvent: Opening");
break;
case MediaPlayer.Event.Buffering: //媒体加载public float getBuffering() 获取加载视频流的进度0-100
int Buffering = (int) event.getBuffering();
if (Buffering >= 100) {
loading.setVisibility(View.GONE);
}else{
if(loading.getVisibility() == View.GONE){
loading.setVisibility(View.VISIBLE);
}
}
break;
case MediaPlayer.Event.EndReached://媒体播放结束
Log.d(TAG, "onEvent: EndReached");
break;
case MediaPlayer.Event.EncounteredError://媒体播放错误
Log.d(TAG, "onEvent: EncounteredError");
ShowToask("播放错误!");
stop();
break;
case MediaPlayer.Event.TimeChanged://视频时间变化
updateTimeCount += event.getTimeChanged() - currItem.PositionTicks;
currItem.PositionTicks = event.getTimeChanged();
if(updateTimeCount > 20000){
updateTimeCount = 0;
ReportPlayState(JfClient.ReportType.Progress);
}
break;
case MediaPlayer.Event.PositionChanged://视频总时长的百分比
break;
case MediaPlayer.Event.SeekableChanged:
Log.d(TAG, "onEvent: SeekableChanged:" + event.getSeekable());
break;
case MediaPlayer.Event.PausableChanged:
Log.d(TAG, "onEvent: PausableChanged");
break;
case MediaPlayer.Event.LengthChanged:
Log.d(TAG, "onEvent: LengthChanged");
break;
case MediaPlayer.Event.Vout://当图像输出
Log.d(TAG, "onEvent: Vout");
break;
case MediaPlayer.Event.ESAdded:
case MediaPlayer.Event.ESDeleted:
case MediaPlayer.Event.ESSelected:
Log.d(TAG, "onEvent: ES:" + event.type + ":" + event.getEsChangedType() + ":" + event.getEsChangedID());
break;
case MediaPlayer.Event.RecordChanged:
Log.d(TAG, "onEvent: RecordChanged");
break;
}
}
/**
* 初始化控制器
*/
private void initController() {
Controller = findViewById(R.id.Controller); // 总布局
ControllerTop = findViewById(R.id.ControllerTop);
ControllerBottom = findViewById(R.id.ControllerBottom);
ControllerBottomTop = findViewById(R.id.ControllerBottomTop);
ControllerBottomBottom = findViewById(R.id.ControllerBottomBottom);
videoTitle = findViewById(R.id.videoTitle); // 标题
currTime = findViewById(R.id.currTime);
countTime = findViewById(R.id.countTime);
speedBtn = findViewById(R.id.speedBtn);
scaleBtn = findViewById(R.id.scaleBtn);
playPauseBtn = findViewById(R.id.playPauseBtn);
stopBtn = findViewById(R.id.stopBtn);
pauseFlag = findViewById(R.id.pauseFlag);
currPostion = findViewById(R.id.currPostion);
loading = findViewById(R.id.loading);
playPauseBtn.setOnClickListener(this);
stopBtn.setOnClickListener(this);
pauseFlag.setOnClickListener(this);
currPostion.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
boolean rv = false;
int action = keyEvent.getAction();
if(action == 1) { //按键up
int keycode = keyEvent.getKeyCode();
if (keycode == KeyEvent.KEYCODE_DPAD_RIGHT) {
rv = setTimeOnSeekBar(currItem.PositionTicks + (long) (mediaPlayer.getLength() * 0.05));
} else if (keycode == KeyEvent.KEYCODE_DPAD_LEFT) {
rv = setTimeOnSeekBar(currItem.PositionTicks - (long) (mediaPlayer.getLength() * 0.05));
}
}
return rv;
}
});
}
/**
* 初始化字幕和音轨菜单
*/
private void initMenu() {
MediaPlayer.TrackDescription[] subTrackList = mediaPlayer.getSpuTracks();
MediaPlayer.TrackDescription[] audioTrackList = mediaPlayer.getAudioTracks();
playListBtn = findViewById(R.id.playListBtn);
subTracksBtn = findViewById(R.id.subTracksBtn);
audioTracksBtn = findViewById(R.id.audioTracksBtn);
if (JfClient.playList.size() > 1) {
initPlayListMenu();
} else {
playListBtn.setVisibility(View.GONE);
}
if (null != subTrackList && subTrackList.length > 1) {
initSubTrackMenu(subTrackList);
} else {
subTracksBtn.setVisibility(View.GONE);
}
if (null != audioTrackList && audioTrackList.length > 1) {
initAudioTrackMenu(audioTrackList);
} else {
audioTracksBtn.setVisibility(View.GONE);
}
//初始化缩放键
scaleBtn.setOnClickListener(this);
scaleBtn.setText(getVlcScaleTypeName(mediaPlayer.getVideoScale().name()));
scaleTypeMenu = new PopMenu(this, scaleBtn);
MediaPlayer.ScaleType[] scaleTypes = MediaPlayer.ScaleType.values();
for (int i = 0; i < scaleTypes.length; i++) {
PopMenu.menu menu = scaleTypeMenu.add(Type_Scale, i, i, getVlcScaleTypeName(scaleTypes[i].name()));
final int Si = i;
menu.v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
scaleTypeMenu.dismiss();
if (mediaPlayer.getVideoScale() != scaleTypes[Si]) {
mediaPlayer.setVideoScale(scaleTypes[Si]);
scaleBtn.setText(getVlcScaleTypeName(mediaPlayer.getVideoScale().name()));
}
}
});
}
//初始化速率键
speedBtn.setOnClickListener(this);
speedBtn.setText(String.valueOf(mediaPlayer.getRate()));
speedMenu = new PopMenu(this, speedBtn);
for (int i = 0; i < speedRate.length; i++) {
PopMenu.menu menu = speedMenu.add(Type_Speed, i, i, String.valueOf(speedRate[i]));
final int Fi = i;
menu.v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
speedMenu.dismiss();
if (mediaPlayer.getRate() != speedRate[Fi]) {
mediaPlayer.setRate(speedRate[Fi]);
speedBtn.setText(String.valueOf(mediaPlayer.getRate()));
}
}
});
}
}
private void initPlayListMenu() {
playListMenu = new PopMenu(this, playListBtn); //new PopupMenu(this,playListBtn);
for (int i = 0; i < JfClient.playList.size(); i++) {
PopMenu.menu m = playListMenu.add(Type_Playlist, i, i, JfClient.playList.get(i).Name);
m.v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
playListMenu.dismiss();
if (m.id != JfClient.playIndex) {
ReportPlayState(JfClient.ReportType.stop);
JfClient.playIndex = m.id;
play();
}
}
});
}
playListBtn.setOnClickListener(this);
}
/**
* 初始化字幕菜单
*
* @param subTrackList
*/
private void initSubTrackMenu(MediaPlayer.TrackDescription[] subTrackList) {
subTrackMenu = new PopMenu(this, subTracksBtn);
for (int i = 0; i < subTrackList.length; i++) {
PopMenu.menu m = subTrackMenu.add(Type_SubtitleTrack, subTrackList[i].id, i, subTrackList[i].name);
m.v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
subTrackMenu.dismiss();
if (m.id != mediaPlayer.getSpuTrack()) {
mediaPlayer.setSpuTrack(m.id);
}
}
});
}
subTracksBtn.setOnClickListener(this);
}
/**
* 初始化音轨菜单
*
* @param audioTrackList
*/
private void initAudioTrackMenu(MediaPlayer.TrackDescription[] audioTrackList) {
audioTrackMenu = new PopMenu(this, audioTracksBtn);
for (int i = 0; i < audioTrackList.length; i++) {
PopMenu.menu m = audioTrackMenu.add(Type_AudioTrack, audioTrackList[i].id, i, audioTrackList[i].name);
m.v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
audioTrackMenu.dismiss();
if (m.id != mediaPlayer.getAudioTrack()) {
mediaPlayer.setAudioTrack(m.id);
}
}
});
}
audioTracksBtn.setOnClickListener(this);
}
private Handler mhandler = new Handler(Looper.getMainLooper());
private Runnable mUpdateSeekBar = new Runnable() {
@Override
public void run() {
setSeekBar(currItem.PositionTicks);
mhandler.postDelayed(mUpdateSeekBar,1000);
}
};
/**
* 显示控制器
*/
public void Show() {
Show(6);
}
/**
* 显示控制器
*/
public void Show(int sec) {
if (ControllerTop.getVisibility() == View.GONE) {
ControllerTop.setVisibility(View.VISIBLE);
}
if (ControllerBottom.getVisibility() == View.GONE) {
ControllerBottom.setVisibility(View.VISIBLE);
mhandler.postDelayed(mUpdateSeekBar,0);
playPauseBtn.requestFocus();
}
}
/**
* 隐藏控制器
*/
public void Hide() {
if (ControllerTop.getVisibility() == View.VISIBLE) {
ControllerTop.setVisibility(View.GONE);
}
if (ControllerBottom.getVisibility() == View.VISIBLE) {
ControllerBottom.setVisibility(View.GONE);
mhandler.removeCallbacks(mUpdateSeekBar);
}
}
/**
* 设置进度条时间
*/
public void setSeekBar(Long p) {
if (ControllerBottom.getVisibility() == View.VISIBLE) {
if(null != mediaPlayer && mediaPlayer.getLength() > 0){
double i = (double) p / 1000;
long duration = mediaPlayer.getLength();
if (duration > 0) {
long pos = 1000L * p / duration;
currPostion.setProgress((int) pos);
}
currTime.setText(TrickToTime(p));
countTime.setText(TrickToTime(duration));
}
}
}
/**
* 开始播放
*/
public void play() {
if (JfClient.playList.size() > 0) {
if (JfClient.playIndex < JfClient.playList.size()) {
currItem = JfClient.playList.get(JfClient.playIndex);
videoTitle.setText(currItem.Name);
Media media = getMedia();
mediaPlayer.setMedia(media);
media.release();
mediaPlayer.play();
}
} else {
stop();
}
}
private Media getMedia(){
Uri uri = Uri.parse(currItem.Url);
Media media = new Media(libVLC,uri);
media.setHWDecoderEnabled(JfClient.config.isHAACC(),JfClient.config.isFORCE_HAACC());
//media.addOption(":codec=mediacodec_ndk,mediacodec_jni,none"); //硬件加速
if(!JfClient.config.isPlayStartInBegin()){
long startTime = currItem.startPositionTicks / 10000L / 1000L;
if( startTime > 60) media.addOption(":start-time=" + startTime); //设置开始位置
}
return media;
}
/**
* 播放下一集
*/
public void playNext() {
if (JfClient.playIndex < (JfClient.playList.size() - 1)) {
ReportPlayState(JfClient.ReportType.stop);
JfClient.playIndex += 1;
play();
}
}
/**
* 停止播放并结束Activity
*/
public void stop() {
ReportPlayState(JfClient.ReportType.stop);
mediaPlayer.stop();
mediaPlayer.release();
libVLC.release();
finish();
}
/**
* 播放或暂停
*/
public void playOrpause() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
pauseFlag.setVisibility(View.VISIBLE);
} else {
mediaPlayer.play();
}
}
/**
* 设置播放器进度
*/
public boolean setTimeOnSeekBar(Long p) {
if (p < mediaPlayer.getLength() && p > 0) {
mediaPlayer.setTime(p,true);
setSeekBar(p);
}
return true;
}
/**
* 进度转时间
*
* @param trick
* @return
*/
public static String TrickToTime(long trick) {
String time = "";
long totalSeconds = trick / 1000;
long seconds = totalSeconds % 60;
long minutes = (totalSeconds / 60) % 60;
long hours = totalSeconds / 3600;
time = String.format("%02d:%02d:%02d", hours, minutes, seconds);
return time;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (ControllerBottom.getVisibility() == View.GONE) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
Show();
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
mediaPlayer.setTime(mediaPlayer.getTime() + 30000);
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
mediaPlayer.setTime(mediaPlayer.getTime() - 10000);
return true;
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
playOrpause();
return true;
case KeyEvent.KEYCODE_ESCAPE:
case KeyEvent.KEYCODE_BACK:
stop();
return true;
//退出
}
} else {
switch (keyCode) {
case KeyEvent.KEYCODE_ESCAPE:
case KeyEvent.KEYCODE_BACK:
Hide();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onClick(View view) {
int id = view.getId();
if (id == R.id.playPauseBtn) {
playOrpause();
} else if (id == R.id.stopBtn) {
stop();
} else if (id == R.id.subTracksBtn) {
subTrackMenu.show(mediaPlayer.getSpuTrack());
} else if (id == R.id.audioTracksBtn) {
audioTrackMenu.show(mediaPlayer.getAudioTrack());
} else if (id == R.id.playListBtn) {
playListMenu.show(JfClient.playIndex);
} else if (id == R.id.scaleBtn) {
scaleTypeMenu.show(getVlcScaleTypeName(mediaPlayer.getVideoScale().name()));
} else if (id == R.id.speedBtn) {
speedMenu.show(String.valueOf(mediaPlayer.getRate()));
}
}
private void ReportPlayState(JfClient.ReportType type) {
JfClient.ReportPlayBackState(type,currItem.Id,currItem.PositionTicks);
}
@Override
public void finish() {
mhandler.removeCallbacksAndMessages(null);
super.finish();
}
/**
* 根据缩放类型取名称
*
* @param scaleName
* @return
*/
public String getVlcScaleTypeName(String scaleName) {
switch (scaleName) {
case "SURFACE_BEST_FIT":
return "自动";
case "SURFACE_FIT_SCREEN":
return "适应屏幕";
case "SURFACE_FILL":
return "满屏";
case "SURFACE_16_9":
return "16:9";
case "SURFACE_4_3":
return "4:3";
case "SURFACE_ORIGINAL":
return "原始";
}
return "";
}
}

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/icon_loading"
android:pivotX="50%"
android:pivotY="50%" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 KiB

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.51,3.87L15.73,2.1 5.84,12l9.9,9.9 1.77,-1.77L9.38,12l8.13,-8.13z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4L14,7h4L18,3h-6zM10,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10,8.64L15.27,12 10,15.36V8.64M8,5v14l11,-7L8,5z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="96dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="96dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10,16.5l6,-4.5 -6,-4.5zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4,10h12v2L4,12zM4,6h12v2L4,8zM4,14h8v2L4,16zM14,14v6l5,-3z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,18l8.5,-6L6,6v12zM8,9.86L11.03,12 8,14.14L8,9.86zM16,6h2v12h-2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6l-8.5,6zM16,14.14L12.97,12 16,9.86v4.28z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M16,8v8H8V8h8m2,-2H6v12h12V6z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,6h16v12zM6,10h2v2L6,12zM6,14h8v2L6,16zM16,14h2v2h-2zM10,10h8v2h-8z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#36ffffff" />
<corners android:radius="1dp" />
</shape>
</item>
<item>
<bitmap
android:antialias="true"
android:gravity="center"
android:src="@drawable/icon_img_placeholder" />
</item>
</layer-list>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="#00ffffff" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="#1890ff" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape>
<corners android:radius="0dp" />
<stroke android:width="1dp" android:color="@color/color_0CADE2" />
<solid android:color="@color/color_3D3D3D" />
</shape>
</item>
<!-- <item android:state_focused="false">-->
<!-- <shape>-->
<!-- <corners android:radius="0dp" />-->
<!-- <solid android:color="@color/color_6C3D3D3D"/>-->
<!-- </shape>-->
<!-- </item>-->
</selector>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape>
<corners android:radius="1dp" />
<stroke android:width="1dp" android:color="@color/color_BD0CADE2" />
<solid android:color="@color/color_6CFFFFFF" />
</shape>
</item>
<item android:state_focused="false">
<shape>
<corners android:radius="1dp" />
<solid android:color="#00000000"/>
</shape>
</item>
</selector>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape>
<corners android:radius="1dp" />
<stroke android:width="2dp" android:color="@color/color_0CADE2" />
<solid android:color="@color/color_6CFFFFFF" />
</shape>
</item>
<item android:state_focused="false">
<shape>
<corners android:radius="1dp" />
<solid android:color="@color/color_6C3D3D3D"/>
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/actionBar_back"
android:layout_width="20dp"
android:layout_height="match_parent"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:focusableInTouchMode="true"
android:padding="6dp"
android:src="@drawable/ic_outline_arrow_back_ios_48" />
<ImageView
android:id="@+id/actionBar_banner"
android:layout_width="100dp"
android:layout_height="match_parent"
android:scaleType="fitStart"
app:srcCompat="@drawable/banner" />
<TextView
android:id="@+id/actionBar_title"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/button_margin_left"
android:layout_weight="1"
android:gravity="center"
android:text=""
android:textSize="18dp" />
<TextView
android:id="@+id/actionBar_titleTip"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/button_margin_left"
android:layout_weight="1"
android:gravity="center"
android:text=""
android:textSize="18dp" />
<ImageView
android:id="@+id/actionBar_searchBtn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:padding="6dp"
android:background="@drawable/shape_user_focus"
android:src="@drawable/ic_outline_search_48" />
<TextView
android:id="@+id/actionBar_sortBtn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:minWidth="100dp"
android:text="排序"
android:textSize="18dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:visibility="gone" />
<ImageView
android:id="@+id/actionBar_setBtn"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/button_margin_left"
android:padding="6dp"
android:src="@drawable/avatar"
android:focusableInTouchMode="true"
android:focusable="true"
android:scaleType="fitCenter"
android:background="@drawable/shape_user_focus" />
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".CollectionActivity">
<org.sifacai.vlcjellyfin.JRecyclerView
android:id="@+id/mGridView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="26dp"
android:paddingTop="@dimen/padding_border_10"
android:paddingRight="@dimen/padding_border_10"
android:paddingBottom="@dimen/padding_border_10"
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
app:tv_selectedItemIsCentered="true"
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
</LinearLayout>

View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/padding_border"
tools:context=".DetailActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/tvCover"
android:layout_width="420dp"
android:layout_height="620dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/icon_img_placeholder" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/padding_border"
android:paddingTop="6dp"
android:paddingRight="@dimen/padding_border"
android:paddingBottom="6dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="38dp"
android:text="标题:"
android:textColor="@android:color/white"
android:textSize="30dp" />
<TextView
android:id="@+id/tvDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="200dp"
android:layout_marginTop="20dp"
android:textSize="20dp" />
<TextView
android:id="@+id/tvListTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="播放列表:" />
<ImageView
android:id="@+id/tvPlay"
android:layout_width="160dp"
android:layout_height="60dp"
android:layout_marginLeft="50dp"
android:background="@drawable/shape_user_focus_vholder"
android:focusable="true"
android:padding="10dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_outline_play_circle_outline_128"
android:visibility="gone" />
<org.sifacai.vlcjellyfin.JRecyclerView
android:id="@+id/mGridView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:padding="@dimen/padding_border"
android:visibility="gone"
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
app:tv_selectedItemIsCentered="true"
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/tvPersonLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone" >
<TextView
android:text="演员表:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"/>
<org.sifacai.vlcjellyfin.JRecyclerView
android:id="@+id/mPersonGridView"
android:layout_width="match_parent"
android:layout_height="312dp"
android:clipChildren="false"
android:clipToPadding="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:padding="@dimen/padding_border"
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
app:tv_selectedItemIsCentered="true"
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:paddingLeft="60dp"
android:paddingRight="60dp"
android:orientation="horizontal" >
<EditText
android:id="@+id/searchTermEdit"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:maxLength="20"
android:inputType="text"
android:lines="1"
android:focusable="true"
android:tooltipText="关键字" />
<ImageView
android:id="@+id/searchBtn"
android:layout_width="100dp"
android:layout_height="40dp"
android:layout_marginLeft="@dimen/button_margin_left"
android:focusable="true"
android:background="@drawable/shape_user_focus"
android:src="@drawable/ic_outline_search_48" />
</LinearLayout>
<org.sifacai.vlcjellyfin.JRecyclerView
android:id="@+id/mGridView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="@dimen/padding_border"
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
app:tv_selectedItemIsCentered="true"
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
</LinearLayout>

View File

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".VlcPlayerActivity">
<org.videolan.libvlc.util.VLCVideoLayout
android:id="@+id/VideoView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/Controller"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ControllerTop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="@color/color_99000000"
android:orientation="horizontal"
android:paddingLeft="30dp"
android:paddingTop="10dp"
android:paddingRight="30dp"
android:paddingBottom="10dp"
android:visibility="gone">
<TextView
android:id="@+id/videoTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题"
android:textColor="#99FFFFFF"
android:textSize="36dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ControllerBottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/color_99000000"
android:gravity="bottom"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:id="@+id/ControllerBottomTop"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/currTime"
android:layout_width="160dp"
android:layout_height="60dp"
android:gravity="center"
android:text="00:00:00"
android:textSize="26dp" />
<SeekBar
android:id="@+id/currPostion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:max="1000"
android:maxHeight="26dp"
android:minHeight="26dp"
android:progress="0" />
<TextView
android:id="@+id/countTime"
android:layout_width="160dp"
android:layout_height="60dp"
android:gravity="center"
android:text="00:00:00"
android:textSize="26dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ControllerBottomBottom"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingBottom="20dp">
<ImageView
android:id="@+id/playPauseBtn"
android:layout_width="@dimen/button_width"
android:layout_height="@dimen/button_height"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:src="@drawable/ic_outline_play_arrow_48" />
<ImageView
android:id="@+id/stopBtn"
android:layout_width="@dimen/button_width"
android:layout_height="@dimen/button_height"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:src="@drawable/ic_outline_stop_48" />
<TextView
android:id="@+id/speedBtn"
android:layout_width="wrap_content"
android:layout_height="@dimen/button_height"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:gravity="center"
android:minWidth="@dimen/button_width"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="@dimen/title_size" />
<TextView
android:id="@+id/scaleBtn"
android:layout_width="wrap_content"
android:layout_height="@dimen/button_height"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:gravity="center"
android:minWidth="@dimen/button_width"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="@dimen/title_size" />
<ImageView
android:id="@+id/subTracksBtn"
android:layout_width="@dimen/button_width"
android:layout_height="@dimen/button_height"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:src="@drawable/ic_outline_subtitles_48" />
<ImageView
android:id="@+id/audioTracksBtn"
android:layout_width="@dimen/button_width"
android:layout_height="@dimen/button_height"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:src="@drawable/ic_outline_audiotrack_48" />
<ImageView
android:id="@+id/playListBtn"
android:layout_width="@dimen/button_width"
android:layout_height="@dimen/button_height"
android:layout_marginLeft="@dimen/button_margin_left"
android:background="@drawable/shape_user_focus"
android:focusable="true"
android:src="@drawable/ic_outline_playlist_play_48" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
<ImageView
android:id="@+id/pauseFlag"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_pause_circle_outline_48"
android:visibility="gone" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_gravity="center" />
</FrameLayout>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/padding_border"
android:paddingTop="0dp">
<EditText
android:id="@+id/dialog_login_url"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_text_height"
android:inputType="text"
android:lines="1"
android:nextFocusDown="@id/dialog_login_un"
android:hint="服务器地址"/>
<EditText
android:id="@+id/dialog_login_un"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_text_height"
android:inputType="text"
android:lines="1"
android:nextFocusDown="@id/dialog_login_pw"
android:hint="用户名"/>
<EditText
android:id="@+id/dialog_login_pw"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_text_height"
android:inputType="text"
android:lines="1"
android:nextFocusDown="@id/dialog_login_save"
android:hint="密码"/>
<CheckBox
android:id="@+id/dialog_login_save"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_text_height"
android:focusable="true"
android:background="@drawable/shape_user_focus"
android:nextFocusDown="@id/dialog_login_submit"
android:text="保存用户" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_text_height"
android:orientation="horizontal">
<TextView
android:id="@+id/dialog_login_submit"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center"
android:focusable="true"
android:background="@drawable/shape_user_focus"
android:text="确定"/>
<TextView
android:id="@+id/dialog_login_cancel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center"
android:focusable="true"
android:background="@drawable/shape_user_focus"
android:text="取消"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<org.sifacai.vlcjellyfin.JRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="20dp"
app:tv_layoutManager="V7LinearLayoutManager"
app:tv_horizontalSpacingWithMargins="10dp"
app:tv_selectedItemIsCentered="true">
</org.sifacai.vlcjellyfin.JRecyclerView>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_user_focus_vholder"
android:focusable="true"
android:padding="@dimen/vh_padding_border">
<FrameLayout
android:layout_width="@dimen/horizon_cover_width"
android:layout_height="@dimen/horizon_cover_height">
<ImageView
android:id="@+id/ivThumb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:scaleType="fitXY" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/color_99000000"
android:padding="1dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:padding="0dp"
android:gravity="center"
android:singleLine="true" />
<SeekBar
android:id="@+id/tvPlayedPercentage"
android:layout_width="match_parent"
android:layout_height="2dp"
android:max="100"
android:progress="0"
android:layout_gravity="bottom"
android:paddingEnd="0dp"
android:paddingStart="0dp"
android:thumb="@null"
android:progressDrawable="@drawable/played_percentage"
android:visibility="gone"/>
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_user_focus_vholder"
android:focusable="true"
android:padding="@dimen/vh_padding_border">
<FrameLayout
android:layout_width="@dimen/vertical_cover_width"
android:layout_height="@dimen/vertical_cover_height">
<ImageView
android:id="@+id/ivThumb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:scaleType="fitXY" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/color_99000000"
android:padding="1dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:padding="0dp"
android:gravity="center"
android:singleLine="true" />
<SeekBar
android:id="@+id/tvPlayedPercentage"
android:layout_width="match_parent"
android:layout_height="2dp"
android:max="100"
android:progress="0"
android:layout_gravity="bottom"
android:paddingEnd="0dp"
android:paddingStart="0dp"
android:thumb="@null"
android:progressDrawable="@drawable/played_percentage"
android:visibility="gone"/>
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".HomeActivity">
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:fadingEdge="vertical">
<LinearLayout
android:id="@+id/tvItems"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="@dimen/padding_border_10"
android:paddingRight="@dimen/padding_border_10"/>
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:background="@color/color_6CFFFFFF"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progressBar1"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
<TextView
android:id="@+id/progressText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:textSize="@dimen/title_size"
android:text=""
android:visibility="gone"/>
</LinearLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="0dp"
android:layout_margin="0dp">
<LinearLayout
android:id="@+id/popitemContiner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:orientation="vertical"
android:padding="0dp" />
</ScrollView>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minWidth="100dp"
android:maxWidth="300dp"
android:padding="10dp"
android:layout_margin="0dp"
android:background="@drawable/popmenu_focus"
android:focusable="true" >
</TextView>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:orderInCategory="0"
android:title="设置选项:" />
<item
android:id="@+id/actionBar_option_PlayStartInBegin"
android:orderInCategory="6"
android:checkable="true"
android:title="从头开始播放" />
<item
android:id="@+id/actionBar_option_HAACC"
android:orderInCategory="7"
android:checkable="true"
android:title="硬解解码" />
<item
android:id="@+id/actionBar_option_FORCE_HAACC"
android:orderInCategory="8"
android:checkable="true"
android:title="强制硬解解码" />
<item
android:id="@+id/actionBar_option_ExtensionPlayer"
android:orderInCategory="9"
android:checkable="true"
android:title="调用外部播放器" />
<item
android:id="@+id/activeBar_option_logout"
android:orderInCategory="99"
android:title="注销" />
</menu>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="color_6200EE">#6200EE</color>
<color name="color_3700B3">#3700B3</color>
<color name="color_03DAC5">#03DAC5</color>
<color name="color_1890FF">#1890FF</color>
<color name="color_FFB800">#FF6600</color>
<color name="color_00FF0A">#00FF0A</color>
<color name="color_CBF46A">#CBF46A</color>
<color name="color_FF90CA09">#90CA09</color>
<color name="color_BD0CADE2">#BD0CADE2</color>
<color name="color_FF0057">#FF0057</color>
<color name="color_FF5F00">#FF5F00</color>
<color name="color_3D3D3D">#3D3D3D</color>
<color name="color_32364E">#32364E</color>
<color name="color_6632364E">#6632364E</color>
<color name="color_CCFFFFFF">#BBFFFFFF</color>
<color name="color_0CADE2">#0CADE2</color>
<color name="color_6CFFFFFF">#6CFFFFFF</color>
<color name="color_66000000">#66000000</color>
<color name="color_99000000">#99000000</color>
<color name="color_CC000000">#CC000000</color>
<color name="color_02F8E1">#02F8E1</color>
<color name="color_353744">#353744</color>
<color name="color_CC353744">#CC353744</color>
<color name="color_6C3D3D3D">#6c3d3d3d</color>
<color name="color_BBFFFFFF">#bbffffff</color>
<color name="color_FFFFFF">#ffffffff</color>
</resources>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="title_height">32dp</dimen>
<dimen name="title_size">22dp</dimen>
<dimen name="horizon_cover_width">400dp</dimen>
<dimen name="horizon_cover_height">228dp</dimen>
<dimen name="vertical_cover_width">180dp</dimen>
<dimen name="vertical_cover_height">270dp</dimen>
<dimen name="focus_border">1dp</dimen>
<dimen name="radius">5dp</dimen>
<dimen name="mGrid_padding_left">20dp</dimen>
<dimen name="mGrid_padding_top">20dp</dimen>
<dimen name="mGrid_padding_right">20dp</dimen>
<dimen name="mGrid_padding_bottom">20dp</dimen>
<dimen name="padding_border_10">10dp</dimen>
<dimen name="padding_border">20dp</dimen>
<dimen name="vh_space_width">10dp</dimen>
<dimen name="vh_padding_border">2dp</dimen>
<dimen name="drawable_padding_s">6dp</dimen>
<dimen name="drawable_padding_b">12dp</dimen>
<!-- 登录框 -->
<dimen name="dialog_text_height">48dp</dimen>
<!-- 播放控制器 -->
<dimen name="time_text_size">26dp</dimen>
<dimen name="button_height">60dp</dimen>
<dimen name="button_width">60dp</dimen>
<dimen name="button_margin_left">26dp</dimen>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">VlcJellyfin</string>
</resources>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="jellyfin" parent="Theme.AppCompat">
<!-- hide title bar -->
<!-- <item name="windowNoTitle">true</item>-->
<!-- <item name="android:background">@drawable/bg_purplehaze</item>-->
<item name="android:windowBackground">@drawable/bg_blueradiance</item>
<item name="actionBarStyle">@style/JyfActionBar</item>
<item name="android:textSize">22dp</item>
<!-- 默认 Button,TextView的文字颜色 -->
<item name="android:textColor">@android:color/white</item>
<!-- 默认 EditView 输入框字体的颜色 -->
<!-- <item name="android:editTextColor">#E6E6FA</item>-->
<!-- RadioButton checkbox等控件的文字 -->
<!-- <item name="android:textColorPrimaryDisableOnly">#1C71A9</item>-->
<!-- 应用的主要文字颜色,actionBar的标题文字默认使用该颜色 -->
<item name="android:textColorPrimary">@android:color/white</item>
<!-- 辅助的文字颜色一般比textColorPrimary的颜色弱一点用于一些弱化的表示 -->
<item name="android:textColorSecondary">#ffffff</item>
</style>
<style name="JyfActionBar" parent="@android:style/Widget.Holo.Light.ActionBar">
<!-- <item name="android:background">#00000000</item>-->
</style>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,17 @@
package org.sifacai.vlcjellyfin;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

9
build.gradle Normal file
View File

@ -0,0 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.library' version '7.2.2' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}

Some files were not shown because too many files have changed in this diff Show More