├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── encodings.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── Screenshot ├── Screenshot_01.jpg ├── Screenshot_02.jpg └── Screenshot_03.jpg ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xyoye │ │ └── smbplayhelper │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xyoye │ │ │ └── smbplayhelper │ │ │ ├── IApplication.java │ │ │ ├── MainActivity.java │ │ │ ├── SmbFileActivity.java │ │ │ ├── service │ │ │ └── SmbService.java │ │ │ ├── smb │ │ │ ├── SmbServer.java │ │ │ ├── SmbServerThread.java │ │ │ └── http │ │ │ │ ├── HttpContentListener.java │ │ │ │ ├── HttpResponse.java │ │ │ │ ├── HttpSocket.java │ │ │ │ └── HttpStatus.java │ │ │ └── utils │ │ │ ├── CommonUtils.java │ │ │ ├── LoadingDialog.java │ │ │ ├── SPUtils.java │ │ │ └── SmbFileAdapter.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── background_line.xml │ │ ├── background_loading.xml │ │ ├── btn_corner_blue.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_smb_file.xml │ │ ├── item_smb.xml │ │ └── layout_loading.xml │ │ ├── menu │ │ └── file_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_smb_folder.png │ │ └── ic_smb_video.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── xyoye │ └── smbplayhelper │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hosts ├── libsmb ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── libs │ ├── asn-one-0.4.0.jar │ ├── bcprov-jdk15on-1.61.jar │ ├── jcifs-ng-2.1.3.jar │ ├── jcifs-origin.jar │ ├── mbassador-1.3.0.jar │ ├── slf4j-api-1.7.25.jar │ ├── smbj-0.9.1.jar │ └── smbj-rpc.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xyoye │ │ └── libsmb │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xyoye │ │ │ └── libsmb │ │ │ ├── SmbManager.java │ │ │ ├── controller │ │ │ ├── Controller.java │ │ │ ├── JCIFSController.java │ │ │ ├── JCIFS_NGController.java │ │ │ ├── SMBJController.java │ │ │ └── SMBJ_RPCController.java │ │ │ ├── exception │ │ │ └── SmbLinkException.java │ │ │ ├── info │ │ │ ├── SmbFileInfo.java │ │ │ ├── SmbLinkInfo.java │ │ │ └── SmbType.java │ │ │ └── utils │ │ │ └── SmbUtils.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── xyoye │ └── libsmb │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 37 | 38 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 93 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmbPlayHelper # 2 | 3 | Android端通过创建本地服务实现局域网内视频播放,相关技术实现的介绍可查看[《关于实现局域网内视频播放》](https://xyoye.github.io/2019/07/25/2019-7-25-%E5%85%B3%E4%BA%8E%E5%AE%9E%E7%8E%B0%E5%B1%80%E5%9F%9F%E7%BD%91%E5%86%85%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE/) 4 | 5 | 1. 从[弹弹Player 概念版](https://github.com/xyoye/DanDanPlayForAndroid)中抽离出来的局域网播放功能,通过创建本地服务,将Smb文件转换成Http协议传输文件流形式实现局域网内视频的播放。 6 | 7 | 2. 项目中使用[smbj-rpc](https://github.com/rapid7/smbj-rpc)、[smbj](https://github.com/hierynomus/smbj)、[jcifs-ng](https://github.com/AgNO3/jcifs-ng)、[jcifs](https://www.jcifs.org/)实现局域网文件浏览,理论上支持SmbV1、SmbV2、SmbV3,项目没有播放器的实现,仅作为服务端,可选取手机上已安装的视频播放播放视频。 8 | 9 | ## 截图 ## 10 | 11 |
12 | 13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /Screenshot/Screenshot_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyoye/SmbPlayHelper/48bbc8fbdbf6b320c3fcf273f45c4ba1dd171b09/Screenshot/Screenshot_01.jpg -------------------------------------------------------------------------------- /Screenshot/Screenshot_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyoye/SmbPlayHelper/48bbc8fbdbf6b320c3fcf273f45c4ba1dd171b09/Screenshot/Screenshot_02.jpg -------------------------------------------------------------------------------- /Screenshot/Screenshot_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyoye/SmbPlayHelper/48bbc8fbdbf6b320c3fcf273f45c4ba1dd171b09/Screenshot/Screenshot_03.jpg -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.2" 6 | defaultConfig { 7 | applicationId "com.xyoye.smbplayhelper" 8 | minSdkVersion 21 9 | targetSdkVersion 29 10 | versionCode 2 11 | versionName "1.1" 12 | multiDexEnabled true 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | compileOptions { 22 | sourceCompatibility = '1.8' 23 | targetCompatibility = '1.8' 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(include: ['*.jar'], dir: 'libs') 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 31 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 35 | implementation 'com.android.support:multidex:1.0.3' 36 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46' 37 | implementation project(':libsmb') 38 | } 39 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xyoye/smbplayhelper/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xyoye.smbjdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/IApplication.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import java.util.concurrent.Executor; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | 10 | /** 11 | * Created by xyoye on 2019/12/24. 12 | */ 13 | 14 | public class IApplication extends Application { 15 | 16 | private static Application application; 17 | private static ExecutorService executorService; 18 | 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | application = this; 23 | executorService = Executors.newSingleThreadExecutor(); 24 | } 25 | 26 | public static Context _getContext(){ 27 | return application.getApplicationContext(); 28 | } 29 | 30 | public static ExecutorService getExecutor(){ 31 | return executorService; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.widget.CheckBox; 7 | import android.widget.EditText; 8 | import android.widget.Toast; 9 | 10 | import androidx.appcompat.app.ActionBar; 11 | import androidx.appcompat.app.AppCompatActivity; 12 | 13 | import com.xyoye.libsmb.SmbManager; 14 | import com.xyoye.libsmb.info.SmbLinkInfo; 15 | import com.xyoye.smbplayhelper.utils.LoadingDialog; 16 | import com.xyoye.smbplayhelper.utils.SPUtils; 17 | 18 | /** 19 | * 登录界面 20 | */ 21 | public class MainActivity extends AppCompatActivity { 22 | private EditText ipEt, accountEt, passwordEt, domainEt, shareEt; 23 | private CheckBox isAnonymousCb; 24 | private CheckBox smbJ_RPCCb, smbJCb, jcifs_NGCb, jcifsCb; 25 | private LoadingDialog loadingDialog; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_main); 31 | 32 | if (getSupportActionBar() != null) { 33 | ActionBar actionBar = getSupportActionBar(); 34 | actionBar.setDisplayHomeAsUpEnabled(false); 35 | actionBar.setDisplayShowTitleEnabled(true); 36 | } 37 | 38 | setTitle("SmbPlayHelper"); 39 | loadingDialog = new LoadingDialog(this, "登录中"); 40 | 41 | ipEt = findViewById(R.id.ip_et); 42 | accountEt = findViewById(R.id.account_et); 43 | passwordEt = findViewById(R.id.password_et); 44 | domainEt = findViewById(R.id.domain_et); 45 | shareEt = findViewById(R.id.share_et); 46 | isAnonymousCb = findViewById(R.id.anonymous_cb); 47 | 48 | smbJ_RPCCb = findViewById(R.id.smb_rpc_cb); 49 | smbJCb = findViewById(R.id.smb_cb); 50 | jcifs_NGCb = findViewById(R.id.jcifs_ng_cb); 51 | jcifsCb = findViewById(R.id.jcifs_cb); 52 | 53 | smbJ_RPCCb.setChecked(SPUtils.getInstance().getBoolean("smb_j_rpc_check", true)); 54 | smbJCb.setChecked(SPUtils.getInstance().getBoolean("smb_j_check", true)); 55 | jcifs_NGCb.setChecked(SPUtils.getInstance().getBoolean("jcifs_ng_check", true)); 56 | jcifsCb.setChecked(SPUtils.getInstance().getBoolean("jcifs_check", true)); 57 | 58 | ipEt.setText(SPUtils.getInstance().getString("smb_ip")); 59 | accountEt.setText(SPUtils.getInstance().getString("smb_account")); 60 | passwordEt.setText(SPUtils.getInstance().getString("smb_password")); 61 | shareEt.setText(SPUtils.getInstance().getString("smb_share")); 62 | isAnonymousCb.setChecked(SPUtils.getInstance().getBoolean("anonymous_check")); 63 | 64 | findViewById(R.id.login_bt).setOnClickListener(v -> login()); 65 | } 66 | 67 | private void login() { 68 | String ip = ipEt.getEditableText().toString().trim(); 69 | String account = accountEt.getEditableText().toString().trim(); 70 | String password = passwordEt.getEditableText().toString().trim(); 71 | String domain = domainEt.getEditableText().toString().trim(); 72 | String share = shareEt.getEditableText().toString().trim(); 73 | boolean isAnonymous = isAnonymousCb.isChecked(); 74 | 75 | SmbLinkInfo smbLinkInfo = new SmbLinkInfo(); 76 | smbLinkInfo.setIP(ip); 77 | smbLinkInfo.setAccount(account); 78 | smbLinkInfo.setPassword(password); 79 | smbLinkInfo.setDomain(domain); 80 | smbLinkInfo.setRootFolder(share); 81 | smbLinkInfo.setAnonymous(isAnonymous); 82 | 83 | boolean smbJRPCEnable = smbJ_RPCCb.isChecked(); 84 | boolean smbJEnable = smbJCb.isChecked(); 85 | boolean jcifsNGEnable = jcifs_NGCb.isChecked(); 86 | boolean jcifsEnable = jcifsCb.isChecked(); 87 | 88 | 89 | SPUtils.getInstance().putBoolean("smb_j_rpc_check", smbJRPCEnable); 90 | SPUtils.getInstance().putBoolean("smb_j_check", smbJEnable); 91 | SPUtils.getInstance().putBoolean("jcifs_ng_check", jcifsNGEnable); 92 | SPUtils.getInstance().putBoolean("jcifs_check", jcifsEnable); 93 | SPUtils.getInstance().putString("smb_ip", ip); 94 | SPUtils.getInstance().putString("smb_account", account); 95 | SPUtils.getInstance().putString("smb_password", password); 96 | SPUtils.getInstance().putString("smb_share", share); 97 | SPUtils.getInstance().putBoolean("anonymous_check", isAnonymous); 98 | 99 | loadingDialog.show(); 100 | IApplication.getExecutor().submit(() -> { 101 | SmbManager smbManager = SmbManager.getInstance(); 102 | smbManager.setEnable(jcifsNGEnable, smbJRPCEnable, smbJEnable, jcifsEnable); 103 | boolean isSuccess = smbManager.linkStart(smbLinkInfo); 104 | runOnUiThread(() -> { 105 | loadingDialog.dismiss(); 106 | if (isSuccess){ 107 | Log.d(MainActivity.class.getSimpleName(), smbManager.getSmbType() + ": Login Success"); 108 | startActivity(new Intent(MainActivity.this, SmbFileActivity.class)); 109 | } else { 110 | Log.d(MainActivity.class.getSimpleName(), smbManager.getException().getExceptionString()); 111 | Toast.makeText(MainActivity.this, "登录失败", Toast.LENGTH_SHORT).show(); 112 | } 113 | }); 114 | 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/SmbFileActivity.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.appcompat.app.ActionBar; 14 | import androidx.appcompat.app.AppCompatActivity; 15 | import androidx.recyclerview.widget.LinearLayoutManager; 16 | import androidx.recyclerview.widget.RecyclerView; 17 | 18 | import com.xyoye.libsmb.SmbManager; 19 | import com.xyoye.libsmb.info.SmbFileInfo; 20 | import com.xyoye.smbplayhelper.service.SmbService; 21 | import com.xyoye.smbplayhelper.smb.SmbServer; 22 | import com.xyoye.smbplayhelper.utils.CommonUtils; 23 | import com.xyoye.smbplayhelper.utils.SmbFileAdapter; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | /** 29 | * 局域网文件浏览界面 30 | */ 31 | 32 | public class SmbFileActivity extends AppCompatActivity { 33 | 34 | private TextView pathTv; 35 | 36 | private List smbFileList; 37 | private SmbFileAdapter smbFileAdapter; 38 | 39 | private SmbManager smbManager; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_smb_file); 45 | 46 | initView(); 47 | 48 | smbManager = SmbManager.getInstance(); 49 | 50 | startService(); 51 | 52 | getSelfData(); 53 | } 54 | 55 | @Override 56 | public boolean onCreateOptionsMenu(Menu menu) { 57 | getMenuInflater().inflate(R.menu.file_menu, menu); 58 | return super.onCreateOptionsMenu(menu); 59 | } 60 | 61 | @Override 62 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 63 | if (item.getItemId() == android.R.id.home) { 64 | IApplication.getExecutor().submit(() -> { 65 | SmbManager.getInstance().getController().release(); 66 | SmbFileActivity.this.finish(); 67 | }); 68 | } else if (item.getItemId() == R.id.previous_item) { 69 | getParentData(); 70 | } 71 | return super.onOptionsItemSelected(item); 72 | } 73 | 74 | private void initView() { 75 | if (getSupportActionBar() != null) { 76 | ActionBar actionBar = getSupportActionBar(); 77 | actionBar.setDisplayHomeAsUpEnabled(true); 78 | actionBar.setDisplayShowTitleEnabled(true); 79 | } 80 | 81 | setTitle("文件列表"); 82 | 83 | pathTv = findViewById(R.id.path_tv); 84 | 85 | smbFileList = new ArrayList<>(); 86 | smbFileAdapter = new SmbFileAdapter(R.layout.item_smb, smbFileList); 87 | smbFileAdapter.setOnItemChildClickListener((adapter, view, position) -> { 88 | String fileName = smbFileList.get(position).getFileName(); 89 | if (smbFileList.get(position).isDirectory()) { 90 | openDirectory(fileName); 91 | } else { 92 | openFile(fileName); 93 | } 94 | }); 95 | 96 | RecyclerView recyclerView = findViewById(R.id.rv); 97 | recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 98 | recyclerView.setAdapter(smbFileAdapter); 99 | } 100 | 101 | /** 102 | * 获取当前文件文件列表 103 | */ 104 | private void getSelfData() { 105 | IApplication.getExecutor().submit(() -> { 106 | List fileList = smbManager.getController().getSelfList(); 107 | String currentPath = smbManager.getController().getCurrentPath(); 108 | String type = smbManager.getController().getClass().getSimpleName(); 109 | runOnUiThread(() -> { 110 | smbFileList.clear(); 111 | smbFileList.addAll(fileList); 112 | smbFileAdapter.notifyDataSetChanged(); 113 | pathTv.setText(currentPath); 114 | setTitle(type.replace("Controller", "")); 115 | }); 116 | }); 117 | } 118 | 119 | /** 120 | * 获取父目录文件列表 121 | */ 122 | private void getParentData() { 123 | if (smbManager.getController().isRootDir()) { 124 | showToast("无父目录"); 125 | return; 126 | } 127 | 128 | IApplication.getExecutor().submit(() -> { 129 | List fileList = smbManager.getController().getParentList(); 130 | String currentPath = smbManager.getController().getCurrentPath(); 131 | runOnUiThread(() -> { 132 | smbFileList.clear(); 133 | smbFileList.addAll(fileList); 134 | smbFileAdapter.notifyDataSetChanged(); 135 | pathTv.setText(currentPath); 136 | }); 137 | }); 138 | } 139 | 140 | /** 141 | * 打开文件夹 142 | */ 143 | private void openDirectory(String dirName) { 144 | IApplication.getExecutor().submit(() -> { 145 | List fileList = smbManager.getController().getChildList(dirName); 146 | String currentPath = smbManager.getController().getCurrentPath(); 147 | runOnUiThread(() -> { 148 | smbFileList.clear(); 149 | smbFileList.addAll(fileList); 150 | smbFileAdapter.notifyDataSetChanged(); 151 | pathTv.setText(currentPath); 152 | }); 153 | }); 154 | } 155 | 156 | /** 157 | * 打开文件 158 | */ 159 | private void openFile(String fileName) { 160 | if (!CommonUtils.isMediaFile(fileName)) { 161 | showToast("不是可播放的视频文件"); 162 | return; 163 | } 164 | 165 | //文件Url由开启监听的IP和端口及视频地址组成 166 | String httpUrl = "http://" + SmbServer.SMB_IP + ":" + SmbServer.SMB_PORT; 167 | String videoUrl = httpUrl + "/smb/" + fileName; 168 | SmbServer.SMB_FILE_NAME = fileName; 169 | 170 | Uri uri = Uri.parse(videoUrl); 171 | Intent intent = new Intent(Intent.ACTION_VIEW); 172 | intent.setDataAndType(uri, "video/*"); 173 | startActivity(intent); 174 | } 175 | 176 | /** 177 | * 启动后台服务 178 | */ 179 | private void startService() { 180 | Intent intent = new Intent(SmbFileActivity.this, SmbService.class); 181 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 182 | startForegroundService(intent); 183 | } else { 184 | startService(intent); 185 | } 186 | } 187 | 188 | private void showToast(String msg) { 189 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/service/SmbService.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.service; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.Service; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.os.Build; 10 | import android.os.IBinder; 11 | 12 | import androidx.core.app.NotificationCompat; 13 | 14 | import com.xyoye.smbplayhelper.R; 15 | import com.xyoye.smbplayhelper.smb.SmbServer; 16 | 17 | /** 18 | * Created by xyoye on 2019/7/23. 19 | * 20 | * 局域网视频播放服务 21 | */ 22 | 23 | public class SmbService extends Service { 24 | private int NOTIFICATION_ID = 1001; 25 | 26 | private SmbServer smbServer = null; 27 | private NotificationManager notificationManager; 28 | 29 | @Override 30 | public IBinder onBind(Intent intent) { 31 | return null; 32 | } 33 | 34 | @Override 35 | public int onStartCommand(Intent intent, int flags, int startId) { 36 | notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 38 | NotificationChannel channel = new NotificationChannel("com.xyoye.smbplayhelper.smbservice.playchannel", "SMB服务", NotificationManager.IMPORTANCE_LOW); 39 | channel.enableVibration(false); 40 | channel.setVibrationPattern(new long[]{0}); 41 | channel.enableLights(false); 42 | channel.setSound(null, null); 43 | if (notificationManager != null) { 44 | notificationManager.createNotificationChannel(channel); 45 | } 46 | } 47 | startForeground(NOTIFICATION_ID, buildNotification()); 48 | return super.onStartCommand(intent, flags, startId); 49 | } 50 | 51 | @Override 52 | public void onCreate() { 53 | super.onCreate(); 54 | smbServer = new SmbServer(); 55 | smbServer.start(); 56 | 57 | } 58 | 59 | private Notification buildNotification() { 60 | Notification.Builder builder = new Notification.Builder(this) 61 | .setSmallIcon(R.mipmap.ic_launcher) 62 | .setContentTitle("SmbPlayHelper") 63 | .setContentText("已开启SMB服务") 64 | .setPriority(Notification.PRIORITY_DEFAULT) 65 | .setContentIntent(null) 66 | .setWhen(System.currentTimeMillis()) 67 | .setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) 68 | .setVibrate(new long[]{0}) 69 | .setSound(null); 70 | 71 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 72 | builder.setChannelId("com.xyoye.smbplayhelper.smbservice.playchannel"); 73 | } 74 | Notification notify = builder.build(); 75 | notify.flags = Notification.FLAG_FOREGROUND_SERVICE; 76 | return notify; 77 | } 78 | 79 | @Override 80 | public void onDestroy() { 81 | super.onDestroy(); 82 | notificationManager.cancel(NOTIFICATION_ID); 83 | if (smbServer != null){ 84 | smbServer.stopSmbServer(); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/smb/SmbServer.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.smb; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.xyoye.libsmb.SmbManager; 6 | import com.xyoye.smbplayhelper.smb.http.HttpContentListener; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.net.InetAddress; 12 | import java.net.ServerSocket; 13 | import java.net.Socket; 14 | 15 | /** 16 | * Created by xyoye on 2019/7/18. 17 | * 18 | * 接收请求 19 | */ 20 | 21 | public class SmbServer extends Thread implements HttpContentListener { 22 | //smb绑定的文件名 23 | public static String SMB_FILE_NAME; 24 | //smb绑定的本地端口 25 | public static int SMB_PORT = 2222; 26 | //smb绑定的本地IP 27 | public static String SMB_IP = "127.0.0.1"; 28 | 29 | //用于接收客户端(播放器)请求的Socket 30 | private ServerSocket serverSocket = null; 31 | 32 | public SmbServer() { 33 | 34 | } 35 | 36 | public void stopSmbServer() { 37 | if (serverSocket != null) { 38 | try { 39 | serverSocket.close(); 40 | serverSocket = null; 41 | SMB_PORT = 2222; 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public void run() { 50 | super.run(); 51 | 52 | //创建ServerSocket 53 | int retryCount = 0; 54 | int port = 2222; 55 | while (!createServerSocket(port)) { 56 | retryCount++; 57 | if (retryCount > 100) { 58 | return; 59 | } 60 | port++; 61 | } 62 | 63 | //在ServerSocket关闭之前一直监听请求 64 | while (!serverSocket.isClosed()){ 65 | try { 66 | Socket socket = serverSocket.accept(); 67 | socket.setSoTimeout(getTimeOut()); 68 | //接收到请求后,新建线程处理请求 69 | new SmbServerThread(socket, this).start(); 70 | }catch (Exception e){ 71 | e.printStackTrace(); 72 | break; 73 | } 74 | } 75 | } 76 | 77 | private synchronized int getTimeOut(){ 78 | return 15 * 1000; 79 | } 80 | 81 | //创建ServerSocket 82 | private boolean createServerSocket(int port) { 83 | if (serverSocket != null) { 84 | return true; 85 | } 86 | try { 87 | SMB_PORT = port; 88 | serverSocket = new ServerSocket(SMB_PORT, 0, InetAddress.getByName(SMB_IP)); 89 | return true; 90 | } catch (IOException e) { 91 | return false; 92 | } 93 | } 94 | 95 | @Override 96 | //获取视频内容 97 | public InputStream getContentInputStream() { 98 | return SmbManager.getInstance().getController().getFileInputStream(SMB_FILE_NAME); 99 | } 100 | 101 | @Override 102 | //获取视频格式 103 | public String getContentType() { 104 | if (TextUtils.isEmpty(SMB_FILE_NAME)) 105 | return ""; 106 | int lastPoi = SMB_FILE_NAME.lastIndexOf('.'); 107 | int lastSep = SMB_FILE_NAME.lastIndexOf(File.separator); 108 | if (lastPoi == -1 || lastSep >= lastPoi) return ""; 109 | return "." +SMB_FILE_NAME.substring(lastPoi + 1); 110 | } 111 | 112 | @Override 113 | //获取视频长度 114 | public long getContentLength() { 115 | return SmbManager.getInstance().getController().getFileLength(SMB_FILE_NAME); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/smb/SmbServerThread.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.smb; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.xyoye.smbplayhelper.smb.http.HttpContentListener; 6 | import com.xyoye.smbplayhelper.smb.http.HttpResponse; 7 | import com.xyoye.smbplayhelper.smb.http.HttpSocket; 8 | import com.xyoye.smbplayhelper.smb.http.HttpStatus; 9 | 10 | import java.io.BufferedInputStream; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | import java.net.Socket; 16 | 17 | /** 18 | * Created by xyoye on 2019/7/18. 19 | *

20 | * 处理请求,返回响应 21 | */ 22 | 23 | public class SmbServerThread extends Thread { 24 | //包含请求内容的Socket 25 | private Socket socket; 26 | //获取视频内容及信息的接口 27 | private HttpContentListener httpContent; 28 | 29 | SmbServerThread(Socket socket, HttpContentListener httpContentListener) { 30 | this.socket = socket; 31 | this.httpContent = httpContentListener; 32 | } 33 | 34 | @Override 35 | public void run() { 36 | HttpSocket httpSocket = new HttpSocket(socket); 37 | if (!httpSocket.open()) { 38 | return; 39 | } 40 | 41 | printLog("----- read request thread start -----"); 42 | 43 | long[] requestRange = getRangeByRequestHeader(httpSocket); 44 | while (requestRange != null) { 45 | handleHttpRequest(httpSocket, requestRange); 46 | requestRange = getRangeByRequestHeader(httpSocket); 47 | } 48 | httpSocket.close(); 49 | } 50 | 51 | //从请求头中读取range信息 52 | private long[] getRangeByRequestHeader(HttpSocket socket) { 53 | printLog("----- read request header -----"); 54 | InputStream inputStream = socket.getInputStream(); 55 | BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); 56 | 57 | String firstLine = readRequestHeaderLine(bufferedInputStream); 58 | if (TextUtils.isEmpty(firstLine)) { 59 | printLog("----- request header is empty -----"); 60 | return null; 61 | } 62 | 63 | String headerLine = readRequestHeaderLine(bufferedInputStream); 64 | while (!TextUtils.isEmpty(headerLine)) { 65 | //Range : byte- 66 | int colonIdx = headerLine.indexOf(':'); 67 | if (colonIdx > 0) { 68 | //Range 69 | String name = new String(headerLine.getBytes(), 0, colonIdx); 70 | //(byte-) (byte 1-123) (byte -123) 71 | String value = new String(headerLine.getBytes(), colonIdx + 1, headerLine.length() - colonIdx - 1); 72 | if (name.equals("Range")) { 73 | int cutIndex = value.indexOf("="); 74 | value = value.substring(cutIndex + 1); 75 | if (value.contains("-")) { 76 | if (value.startsWith("-")) { 77 | value = "0" + value; 78 | } else if (value.endsWith("-")) { 79 | value = value + "0"; 80 | } 81 | String[] ranges = value.split("-"); 82 | long[] range = new long[2]; 83 | range[0] = Long.valueOf(ranges[0]); 84 | range[1] = Long.valueOf(ranges[1]); 85 | printLog("----- read range success ----- :" + range[0] + "/" + range[1]); 86 | return range; 87 | } 88 | } 89 | } 90 | headerLine = readRequestHeaderLine(bufferedInputStream); 91 | } 92 | return new long[]{0, 0}; 93 | } 94 | 95 | //按行读取头信息 96 | private String readRequestHeaderLine(BufferedInputStream bufferedInputStream) { 97 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 98 | byte[] bytes = new byte[1]; 99 | try { 100 | int readLen = bufferedInputStream.read(bytes); 101 | while (readLen > 0) { 102 | byte n = '\n'; 103 | byte r = '\r'; 104 | if (bytes[0] == n) { 105 | break; 106 | } 107 | if (bytes[0] != r) { 108 | byteArrayOutputStream.write(bytes[0]); 109 | } 110 | readLen = bufferedInputStream.read(bytes); 111 | } 112 | } catch (IOException e) { 113 | e.printStackTrace(); 114 | return ""; 115 | } 116 | 117 | return byteArrayOutputStream.toString(); 118 | } 119 | 120 | //处理请求构建response 121 | private void handleHttpRequest(HttpSocket httpSocket, long[] requestRange) { 122 | printLog("----- handle http request -----"); 123 | // 获取文件流 124 | InputStream contentInputStream = httpContent.getContentInputStream(); 125 | // 获取文件的大小 126 | long contentLength = httpContent.getContentLength(); 127 | // 获取文件类型 128 | String contentType = httpContent.getContentType(); 129 | 130 | long requestRangeStart = requestRange[0]; 131 | long requestRangeEnd = requestRange[1] <= 0 ? contentLength - 1 : requestRange[1]; 132 | 133 | if (contentLength <= 0 || contentType.length() <= 0 || contentInputStream == null) { 134 | printLog("----- handle failed : smb file error -----"); 135 | printLog("----- length:" + contentLength + " stream:" + (contentInputStream == null) + " -----"); 136 | HttpResponse badResponse = new HttpResponse(); 137 | badResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); 138 | badResponse.setContentLength(0); 139 | dispatchResponse(badResponse, httpSocket, 0, 0, true); 140 | return; 141 | } 142 | 143 | if ((requestRangeStart > contentLength) || (requestRangeEnd > contentLength)) { 144 | printLog("-----handle failed : request range error -----"); 145 | HttpResponse badResponse = new HttpResponse(); 146 | badResponse.setStatusCode(HttpStatus.INVALID_RANGE); 147 | badResponse.setContentLength(0); 148 | dispatchResponse(badResponse, httpSocket, 0, 0, true); 149 | return; 150 | } 151 | 152 | printLog("-----handle success : response build -----"); 153 | long responseLength = requestRangeEnd - requestRangeStart + 1; 154 | HttpResponse response = new HttpResponse(); 155 | response.setStatusCode(HttpStatus.PARTIAL_CONTENT); 156 | response.setContentType(contentType); 157 | response.setContentLength(responseLength); 158 | response.setContentInputStream(contentInputStream); 159 | response.setContentRange(requestRangeStart, requestRangeEnd, contentLength); 160 | dispatchResponse(response, httpSocket, requestRangeStart, responseLength, false); 161 | } 162 | 163 | //返回response 164 | private synchronized void dispatchResponse(HttpResponse response, 165 | HttpSocket httpSocket, 166 | long contentOffset, 167 | long contentLength, 168 | boolean badRequest) { 169 | printLog("----- dispatch http response -----"); 170 | 171 | InputStream inputStream = null; 172 | OutputStream outputStream = null; 173 | try { 174 | outputStream = httpSocket.getOutputStream(); 175 | outputStream.write(response.getResponseHeader().getBytes()); 176 | //必须返回!!! 177 | outputStream.write("\r\n".getBytes()); 178 | 179 | //无法处理的请求 180 | if (badRequest) { 181 | outputStream.flush(); 182 | printLog("----- return bad response -----"); 183 | return; 184 | } 185 | 186 | inputStream = response.getContentInputStream(); 187 | if (contentOffset > 0) { 188 | if (inputStream.skip(contentOffset) > 0) { 189 | printLog("----- input stream skip -----:" + contentOffset); 190 | } 191 | } 192 | 193 | int bufferSize = 512 * 1024; 194 | byte[] readBuffer = new byte[bufferSize]; 195 | long readTotalSize = 0; 196 | long readSize = (bufferSize > contentLength) ? contentLength : bufferSize; 197 | int readLen = inputStream.read(readBuffer, 0, (int) readSize); 198 | 199 | printLog("----- send video data start -----:" + contentOffset); 200 | while (readLen > 0 && readTotalSize < contentLength) { 201 | outputStream.write(readBuffer, 0, readLen); 202 | outputStream.flush(); 203 | readTotalSize += readLen; 204 | readSize = (bufferSize > (contentLength - readTotalSize)) 205 | ? (contentLength - readTotalSize) 206 | : bufferSize; 207 | readLen = inputStream.read(readBuffer, 0, (int) readSize); 208 | 209 | printLog("----- send video data success -----:" + readTotalSize + "/" + contentLength); 210 | } 211 | printLog("----- send video data over -----:" + contentOffset); 212 | outputStream.flush(); 213 | } catch (Exception e) { 214 | printLog("----- send video data error -----:" + e.getMessage()); 215 | e.printStackTrace(); 216 | } finally { 217 | try {if (inputStream != null) 218 | inputStream.close(); 219 | if (outputStream != null) 220 | outputStream.close(); 221 | }catch (IOException ignore){ 222 | 223 | } 224 | } 225 | } 226 | 227 | //打印信息 228 | private void printLog(String message) { 229 | System.out.println(message); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/smb/http/HttpContentListener.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.smb.http; 2 | 3 | import java.io.InputStream; 4 | 5 | /** 6 | * Created by xyoye on 2019/7/19. 7 | */ 8 | 9 | public interface HttpContentListener { 10 | /** 11 | * 获取视频流 12 | */ 13 | InputStream getContentInputStream(); 14 | 15 | /** 16 | * 获取视频类型 17 | */ 18 | String getContentType(); 19 | 20 | /** 21 | * 获取视频长度 22 | */ 23 | long getContentLength(); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/smb/http/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.smb.http; 2 | 3 | import java.io.InputStream; 4 | 5 | /** 6 | * Created by xyoye on 2019/7/15. 7 | * 8 | * 响应头封装 9 | */ 10 | public class HttpResponse{ 11 | //内容 12 | private InputStream contentInputStream = null; 13 | 14 | //http状态码 15 | private int statusCode = 0; 16 | //服务名 17 | private String server; 18 | //内容类型 19 | private String contentType; 20 | //内容长度 21 | private String contentLength; 22 | //内容范围 23 | private String contentRange; 24 | 25 | public HttpResponse() { 26 | String OSName = System.getProperty("os.name"); 27 | String OSVersion = System.getProperty("os.version"); 28 | 29 | server = OSName + "/" + OSVersion + " HTTP/1.1"; 30 | contentType = "text/html; charset=\"utf-8\""; 31 | } 32 | 33 | public void setStatusCode(int code) { 34 | statusCode = code; 35 | } 36 | 37 | public void setContentType(String contentType){ 38 | this.contentType = contentType; 39 | } 40 | 41 | public void setContentInputStream(InputStream inputStream){ 42 | contentInputStream = inputStream; 43 | } 44 | 45 | public void setContentLength(long contentLength){ 46 | this.contentLength = String.valueOf(contentLength); 47 | } 48 | 49 | public void setContentRange(long startRange, long endRange, long length){ 50 | String rangeStr = "bytes "; 51 | rangeStr += startRange + "-"; 52 | rangeStr += endRange + "/"; 53 | rangeStr += ((0 < length) ? Long.toString(length) : "*"); 54 | contentRange = rangeStr; 55 | } 56 | 57 | public String getResponseHeader() { 58 | String statusLine = "HTTP/1.1 " + statusCode + " " + HttpStatus.code2String(statusCode); 59 | return statusLine + "\r\n" + 60 | "Server: " + server + "\r\n" + 61 | "Content-Type: " + contentType + "\r\n" + 62 | "Content-Length: " + contentLength + "\r\n" + 63 | "Content-Range: " + contentRange + "\r\n"; 64 | } 65 | 66 | public InputStream getContentInputStream(){ 67 | return contentInputStream; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/smb/http/HttpSocket.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.smb.http; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | import java.net.Socket; 6 | 7 | /** 8 | * Created by xyoye on 2019/7/15. 9 | * 10 | * Socket封装 11 | */ 12 | public class HttpSocket { 13 | private Socket mSocket; 14 | private InputStream socketInputStream = null; 15 | private OutputStream socketOutputStream = null; 16 | 17 | public HttpSocket(Socket socket) { 18 | this.mSocket = socket; 19 | open(); 20 | } 21 | 22 | public boolean open() { 23 | try { 24 | socketInputStream = mSocket.getInputStream(); 25 | socketOutputStream = mSocket.getOutputStream(); 26 | } catch (Exception e) { 27 | e.printStackTrace(); 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | public void close() { 34 | try { 35 | if (socketInputStream != null) { 36 | socketInputStream.close(); 37 | } 38 | if (socketOutputStream != null) { 39 | socketOutputStream.close(); 40 | } 41 | mSocket.close(); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | 47 | @Override 48 | public void finalize() { 49 | close(); 50 | } 51 | 52 | public boolean isClosed(){ 53 | return mSocket.isClosed(); 54 | } 55 | 56 | public InputStream getInputStream() { 57 | return socketInputStream; 58 | } 59 | 60 | public OutputStream getOutputStream() { 61 | return socketOutputStream; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/smb/http/HttpStatus.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.smb.http; 2 | 3 | /** 4 | * Http状态码 5 | */ 6 | public class HttpStatus { 7 | //请求者应当继续提出请求。服务器返回此代码则意味着,服务器已收到了请求的第一部分,现正在等待接收其余部分。 8 | public static final int CONTINUE = 100; 9 | //服务器已成功处理了请求 10 | public static final int OK = 200; 11 | //服务器成功处理了部分 GET 请求 12 | public static final int PARTIAL_CONTENT = 206; 13 | //服务器不理解请求的语法 14 | public static final int BAD_REQUEST = 400; 15 | //服务器找不到请求的网页 16 | public static final int NOT_FOUND = 404; 17 | //服务器未满足请求者在请求中设置的其中一个前提条件。 18 | public static final int PRECONDITION_FAILED = 412; 19 | //所请求的范围无法满足 20 | public static final int INVALID_RANGE = 416; 21 | //服务器遇到错误,无法完成请求 22 | public static final int INTERNAL_SERVER_ERROR = 500; 23 | 24 | // 将状态码转换成提示的字符串 ,如果没有此状态码返回一个空字符串 25 | public static String code2String(int code) { 26 | switch (code) { 27 | case CONTINUE: 28 | return "Continue"; 29 | case OK: 30 | return "OK"; 31 | case PARTIAL_CONTENT: 32 | return "Partial Content"; 33 | case BAD_REQUEST: 34 | return "Bad Request"; 35 | case NOT_FOUND: 36 | return "Not Found"; 37 | case PRECONDITION_FAILED: 38 | return "Precondition Failed"; 39 | case INVALID_RANGE: 40 | return "Invalid Range"; 41 | case INTERNAL_SERVER_ERROR: 42 | return "Internal Server Error"; 43 | } 44 | return ""; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/utils/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * Created by xyoye on 2019/7/23. 9 | */ 10 | 11 | public class CommonUtils { 12 | 13 | /** 14 | * 判断视频格式 15 | */ 16 | public static boolean isMediaFile(String fileName){ 17 | switch (getFileExtension(fileName).toLowerCase()){ 18 | case "3gp": 19 | case "avi": 20 | case "flv": 21 | case "mp4": 22 | case "m4v": 23 | case "mkv": 24 | case "mov": 25 | case "mpeg": 26 | case "mpg": 27 | case "mpe": 28 | case "rm": 29 | case "rmvb": 30 | case "wmv": 31 | case "asf": 32 | case "asx": 33 | case "dat": 34 | case "vob": 35 | case "m3u8": 36 | return true; 37 | default: return false; 38 | } 39 | } 40 | /** 41 | * 获取文件格式 42 | */ 43 | public static String getFileExtension(final String filePath) { 44 | if (TextUtils.isEmpty(filePath)) return ""; 45 | int lastPoi = filePath.lastIndexOf('.'); 46 | int lastSep = filePath.lastIndexOf(File.separator); 47 | if (lastPoi == -1 || lastSep >= lastPoi) return ""; 48 | return filePath.substring(lastPoi + 1); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/utils/LoadingDialog.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.utils; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.text.TextUtils; 7 | import android.widget.TextView; 8 | 9 | import com.xyoye.smbplayhelper.R; 10 | 11 | public class LoadingDialog extends Dialog { 12 | private String msg; 13 | private TextView textView; 14 | 15 | public LoadingDialog(Context context) { 16 | super(context, R.style.LoadingDialog); 17 | } 18 | 19 | public LoadingDialog(Context context, String msg) { 20 | super(context, R.style.LoadingDialog); 21 | this.msg = msg; 22 | } 23 | 24 | public LoadingDialog(Context context, int theme) { 25 | super(context, theme); 26 | } 27 | 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.layout_loading); 33 | 34 | textView = this.findViewById(R.id.msg_tv); 35 | if (!TextUtils.isEmpty(msg)) { 36 | this.setCancelable(false); 37 | textView.setText(msg); 38 | } 39 | } 40 | 41 | public void updateText(String text) { 42 | textView.setText(text); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/utils/SPUtils.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import com.xyoye.smbplayhelper.IApplication; 7 | 8 | public class SPUtils { 9 | private SharedPreferences sharedPreferences; 10 | 11 | private static class Holder { 12 | static SPUtils instance = new SPUtils(); 13 | } 14 | 15 | private SPUtils() { 16 | sharedPreferences = IApplication._getContext() 17 | .getSharedPreferences("date", Context.MODE_PRIVATE); 18 | } 19 | 20 | public static SPUtils getInstance() { 21 | return Holder.instance; 22 | } 23 | 24 | public void putString(String key, String value) { 25 | SharedPreferences.Editor editor = sharedPreferences.edit(); 26 | editor.putString(key, value); 27 | editor.apply(); 28 | } 29 | 30 | public String getString(String key) { 31 | return sharedPreferences.getString(key, ""); 32 | } 33 | 34 | public void putBoolean(String key, boolean value) { 35 | SharedPreferences.Editor editor = sharedPreferences.edit(); 36 | editor.putBoolean(key, value); 37 | editor.apply(); 38 | } 39 | 40 | public boolean getBoolean(String key) { 41 | return sharedPreferences.getBoolean(key, false); 42 | } 43 | 44 | public boolean getBoolean(String key, boolean defValue) { 45 | return sharedPreferences.getBoolean(key, defValue); 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xyoye/smbplayhelper/utils/SmbFileAdapter.java: -------------------------------------------------------------------------------- 1 | package com.xyoye.smbplayhelper.utils; 2 | 3 | import androidx.annotation.LayoutRes; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.chad.library.adapter.base.BaseQuickAdapter; 7 | import com.chad.library.adapter.base.BaseViewHolder; 8 | import com.xyoye.libsmb.info.SmbFileInfo; 9 | import com.xyoye.smbplayhelper.R; 10 | 11 | import java.util.List; 12 | 13 | public class SmbFileAdapter extends BaseQuickAdapter { 14 | 15 | public SmbFileAdapter(@LayoutRes int layoutResId, @Nullable List data) { 16 | super(layoutResId, data); 17 | } 18 | 19 | @Override 20 | protected void convert(BaseViewHolder helper, SmbFileInfo item) { 21 | helper.setImageResource(R.id.iv, item.isDirectory() ? R.mipmap.ic_smb_folder : R.mipmap.ic_smb_video) 22 | .setText(R.id.tv, item.getFileName()) 23 | .addOnClickListener(R.id.item_layout); 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_corner_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 26 | 27 | 32 | 33 | 42 | 43 | 44 | 50 | 51 | 58 | 59 | 65 | 66 | 78 | 79 | 80 | 81 | 88 | 89 | 95 | 96 | 108 | 109 | 110 | 111 | 118 | 119 | 125 | 126 | 138 | 139 | 140 | 141 | 148 | 149 | 155 | 156 | 168 | 169 | 170 | 171 | 178 | 179 | 185 | 186 | 198 | 199 | 200 | 201 | 207 | 208 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 228 | 229 | 236 | 237 | 244 | 245 | 252 | 253 | 260 | 261 | 262 |