├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── spx │ │ └── exoplayertest │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ ├── sogo │ │ │ └── exoplayer │ │ │ │ ├── LdDefaultLoadControl.java │ │ │ │ ├── LdExoPlayerController.java │ │ │ │ ├── PlayerManager.java │ │ │ │ ├── PlayerWrapper.java │ │ │ │ ├── VLog.java │ │ │ │ ├── VUtil.java │ │ │ │ └── VideoUrlCache.java │ │ │ └── spx │ │ │ └── exoplayertest │ │ │ ├── DraggableLayout.java │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ └── VideoPlayActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── ic_play_video_normal.png │ │ ├── ic_replay_normal.png │ │ ├── sg_player_player_btn.png │ │ ├── sg_seek_dot.png │ │ └── sg_stop_btn.png │ │ ├── drawable │ │ ├── bg_video_control.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_video_play.xml │ │ ├── exo_playback_control_view.xml │ │ ├── list_item_layout.xml │ │ └── video_layout.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 │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-v19 │ │ └── styles.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── spx │ └── exoplayertest │ └── ExampleUnitTest.java ├── build.gradle ├── core ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── spx │ │ └── core │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── spx │ └── core │ └── ExampleUnitTest.java ├── gradle.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExoplayerTest 2 | exoplayer demo app 3 | 一个exoplayer2的学习示例. 4 | 5 | 包括的功能点: 6 | 1. 预加载 本地缓存 7 | 2. 列表中播放 8 | 3. 从列表中动画切换至播放页面 9 | 4. 手机非wifi模式下停止预加载 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.spx.exoplayertest" 7 | minSdkVersion 17 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:27.0.0' 24 | implementation 'com.android.support:recyclerview-v7:27.0.0' 25 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 26 | implementation 'com.google.android.exoplayer:exoplayer-core:2.8.3' 27 | // compile 'com.google.android.exoplayer:exoplayer-dash:2.6.0' 28 | implementation 'com.google.android.exoplayer:exoplayer-ui:2.8.3' 29 | 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 33 | } 34 | -------------------------------------------------------------------------------- /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/spx/exoplayertest/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.spx.exoplayertest; 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.spx.exoplayertest", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/sogo/exoplayer/LdDefaultLoadControl.java: -------------------------------------------------------------------------------- 1 | package com.sogo.exoplayer; 2 | 3 | 4 | import com.google.android.exoplayer2.C; 5 | import com.google.android.exoplayer2.LoadControl; 6 | import com.google.android.exoplayer2.Renderer; 7 | import com.google.android.exoplayer2.source.TrackGroupArray; 8 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 9 | import com.google.android.exoplayer2.upstream.Allocator; 10 | import com.google.android.exoplayer2.upstream.DefaultAllocator; 11 | import com.google.android.exoplayer2.util.PriorityTaskManager; 12 | import com.google.android.exoplayer2.util.Util; 13 | 14 | /** 15 | * The default {@link LoadControl} implementation. 16 | */ 17 | public final class LdDefaultLoadControl implements LoadControl { 18 | 19 | /** 20 | * The default minimum duration of media that the player will attempt to ensure is buffered at all 21 | * times, in milliseconds. 22 | */ 23 | public static final int DEFAULT_MIN_BUFFER_MS = 15000; 24 | 25 | /** 26 | * The default maximum duration of media that the player will attempt to buffer, in milliseconds. 27 | */ 28 | public static final int DEFAULT_MAX_BUFFER_MS = 30000; 29 | 30 | /** 31 | * The default duration of media that must be buffered for playback to start or resume following a 32 | * user action such as a seek, in milliseconds. 33 | */ 34 | public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500; 35 | 36 | /** 37 | * The default duration of media that must be buffered for playback to resume after a rebuffer, 38 | * in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user 39 | * action. 40 | */ 41 | public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000; 42 | 43 | private static final int ABOVE_HIGH_WATERMARK = 0; 44 | private static final int BETWEEN_WATERMARKS = 1; 45 | private static final int BELOW_LOW_WATERMARK = 2; 46 | 47 | private final DefaultAllocator allocator; 48 | 49 | private final long minBufferUs; 50 | private final long maxBufferUs; 51 | private final long bufferForPlaybackUs; 52 | private final long bufferForPlaybackAfterRebufferUs; 53 | private final PriorityTaskManager priorityTaskManager; 54 | 55 | private int targetBufferSize; 56 | private boolean isBuffering; 57 | 58 | private boolean userAction; 59 | 60 | /** 61 | * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. 62 | */ 63 | public LdDefaultLoadControl(boolean userAction) { 64 | this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); 65 | this.userAction = userAction; 66 | } 67 | 68 | /** 69 | * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. 70 | * 71 | * @param allocator The {@link DefaultAllocator} used by the loader. 72 | */ 73 | public LdDefaultLoadControl(DefaultAllocator allocator) { 74 | this(allocator, DEFAULT_MIN_BUFFER_MS, DEFAULT_MAX_BUFFER_MS, DEFAULT_BUFFER_FOR_PLAYBACK_MS, 75 | DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); 76 | } 77 | 78 | /** 79 | * Constructs a new instance. 80 | * 81 | * @param allocator The {@link DefaultAllocator} used by the loader. 82 | * @param minBufferMs The minimum duration of media that the player will attempt to ensure is 83 | * buffered at all times, in milliseconds. 84 | * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in 85 | * milliseconds. 86 | * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or 87 | * resume following a user action such as a seek, in milliseconds. 88 | * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for 89 | * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by 90 | * buffer depletion rather than a user action. 91 | */ 92 | public LdDefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, 93 | long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs) { 94 | this(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, 95 | null); 96 | } 97 | 98 | /** 99 | * Constructs a new instance. 100 | * 101 | * @param allocator The {@link DefaultAllocator} used by the loader. 102 | * @param minBufferMs The minimum duration of media that the player will attempt to ensure is 103 | * buffered at all times, in milliseconds. 104 | * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in 105 | * milliseconds. 106 | * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or 107 | * resume following a user action such as a seek, in milliseconds. 108 | * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for 109 | * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by 110 | * buffer depletion rather than a user action. 111 | * @param priorityTaskManager If not null, registers itself as a task with priority 112 | * {@link C#PRIORITY_PLAYBACK} during loading periods, and unregisters itself during draining 113 | * periods. 114 | */ 115 | public LdDefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, 116 | long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs, 117 | PriorityTaskManager priorityTaskManager) { 118 | this.allocator = allocator; 119 | minBufferUs = minBufferMs * 1000L; 120 | maxBufferUs = maxBufferMs * 1000L; 121 | bufferForPlaybackUs = bufferForPlaybackMs * 1000L; 122 | bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; 123 | this.priorityTaskManager = priorityTaskManager; 124 | } 125 | 126 | @Override 127 | public void onPrepared() { 128 | reset(false); 129 | } 130 | 131 | @Override 132 | public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, 133 | TrackSelectionArray trackSelections) { 134 | targetBufferSize = 0; 135 | for (int i = 0; i < renderers.length; i++) { 136 | if (trackSelections.get(i) != null) { 137 | targetBufferSize += Util.getDefaultBufferSize(renderers[i].getTrackType()); 138 | } 139 | } 140 | allocator.setTargetBufferSize(targetBufferSize); 141 | } 142 | 143 | @Override 144 | public void onStopped() { 145 | reset(true); 146 | } 147 | 148 | @Override 149 | public void onReleased() { 150 | reset(true); 151 | } 152 | 153 | @Override 154 | public Allocator getAllocator() { 155 | return allocator; 156 | } 157 | 158 | @Override 159 | public long getBackBufferDurationUs() { 160 | return 0; 161 | } 162 | 163 | @Override 164 | public boolean retainBackBufferFromKeyframe() { 165 | return false; 166 | } 167 | 168 | // @Override 169 | // public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) { 170 | // return false; 171 | // } 172 | 173 | @Override 174 | public boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering) { 175 | long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; 176 | return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs; 177 | } 178 | 179 | // @Override 180 | // public boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering) { 181 | // long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; 182 | // return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs; 183 | // } 184 | 185 | // @Override 186 | public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) { 187 | if (!VUtil.isWifiConnected(VUtil.getApplication())) { 188 | if(!userAction){ 189 | return false; 190 | } 191 | 192 | } 193 | int bufferTimeState = getBufferTimeState(bufferedDurationUs); 194 | boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; 195 | boolean wasBuffering = isBuffering; 196 | isBuffering = bufferTimeState == BELOW_LOW_WATERMARK 197 | || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached); 198 | if (priorityTaskManager != null && isBuffering != wasBuffering) { 199 | if (isBuffering) { 200 | priorityTaskManager.add(C.PRIORITY_PLAYBACK); 201 | } else { 202 | priorityTaskManager.remove(C.PRIORITY_PLAYBACK); 203 | } 204 | } 205 | return isBuffering; 206 | } 207 | 208 | private int getBufferTimeState(long bufferedDurationUs) { 209 | return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK 210 | : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS); 211 | } 212 | 213 | private void reset(boolean resetAllocator) { 214 | targetBufferSize = 0; 215 | if (priorityTaskManager != null && isBuffering) { 216 | priorityTaskManager.remove(C.PRIORITY_PLAYBACK); 217 | } 218 | isBuffering = false; 219 | if (resetAllocator) { 220 | allocator.reset(); 221 | } 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /app/src/main/java/com/sogo/exoplayer/LdExoPlayerController.java: -------------------------------------------------------------------------------- 1 | package com.sogo.exoplayer; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.text.TextUtils; 6 | import android.view.View; 7 | import android.widget.ImageView; 8 | import android.widget.ProgressBar; 9 | import android.widget.Toast; 10 | 11 | import com.google.android.exoplayer2.ExoPlaybackException; 12 | import com.google.android.exoplayer2.PlaybackParameters; 13 | import com.google.android.exoplayer2.Player; 14 | import com.google.android.exoplayer2.SimpleExoPlayer; 15 | import com.google.android.exoplayer2.Timeline; 16 | import com.google.android.exoplayer2.source.TrackGroupArray; 17 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 18 | import com.google.android.exoplayer2.ui.SimpleExoPlayerView; 19 | import com.spx.exoplayertest.R; 20 | 21 | 22 | import java.io.IOException; 23 | 24 | /** 25 | * Created by shaopengxiang on 2017/12/18. 26 | */ 27 | 28 | public class LdExoPlayerController implements PlayerManager.PlayerHolder { 29 | 30 | private static final String TAG = "Player.Controller"; 31 | 32 | private Context mContext; 33 | private int mId; 34 | private String mContent; 35 | private String mVideoUrl; 36 | private String mCoverImgUrl; 37 | 38 | 39 | private SimpleExoPlayerView mPlayerView; 40 | private PlayerWrapper mPlayer; 41 | private ImageView mCoverIv; 42 | private ImageView mStartPlayIv; 43 | 44 | private ProgressBar mProgressBar; 45 | 46 | private long mOnClickTime = 0L; 47 | 48 | public LdExoPlayerController(View itemView) { 49 | mPlayerView = itemView.findViewById(R.id.player_view); 50 | mPlayer = PlayerManager.getInstance().getPlayer(this); 51 | mCoverIv = itemView.findViewById(R.id.video_cover_iv); 52 | mStartPlayIv = itemView.findViewById(R.id.video_startplay_iv); 53 | mProgressBar = itemView.findViewById(R.id.progressBar); 54 | 55 | if (mCoverIv != null) { 56 | mCoverIv.setOnClickListener(clickListener); 57 | } 58 | 59 | mStartPlayIv.setOnClickListener(clickListener); 60 | } 61 | 62 | public void bindData(Context context, int id, String content, String videoUrl, String coverImgUrl) { 63 | this.mContext = context; 64 | this.mId = id; 65 | this.mContent = content; 66 | this.mVideoUrl = videoUrl; 67 | this.mCoverImgUrl = coverImgUrl; 68 | VLog.e(TAG, "bindData[" + id + "]: content:" + content); 69 | // Log.d(TAG, "bindData["+position+"]: video url:"+card.video.getVideoUrl()); 70 | 71 | // ImageUtils.showImage(context, mCoverIv, mCoverImgUrl); 72 | 73 | PlayerManager.getInstance().preAttch(videoUrl, this); 74 | 75 | initViews(); 76 | } 77 | 78 | public void transformIn(String videoUrl){ 79 | PlayerManager instance = PlayerManager.getInstance(); 80 | PlayerWrapper player = instance.getPlayer(null); 81 | player.transformIn(mPlayerView); 82 | } 83 | 84 | public void transformOut(){ 85 | PlayerManager instance = PlayerManager.getInstance(); 86 | PlayerWrapper player = instance.getPlayer(null); 87 | player.transformOut(mPlayerView); 88 | } 89 | 90 | View.OnClickListener clickListener = new View.OnClickListener() { 91 | @Override 92 | public void onClick(View v) { 93 | VLog.d(TAG, "mStartPlayIv onClick: ..."); 94 | start(true); 95 | } 96 | }; 97 | 98 | public void start(boolean userAction) { 99 | mOnClickTime = System.currentTimeMillis(); 100 | mStartPlayIv.setVisibility(View.GONE); 101 | // 点击时初始化player 102 | initPlayer(userAction); 103 | mPlayer.play(); 104 | } 105 | 106 | 107 | private SimpleExoPlayer.VideoListener videoListener = new SimpleExoPlayer.VideoListener() { 108 | @Override 109 | public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { 110 | VLog.d(TAG, "onVideoSizeChanged: ...width:" + width + ", height:" + height 111 | + ", pixelWidthHeightRatio:" + pixelWidthHeightRatio); 112 | } 113 | 114 | @Override 115 | public void onRenderedFirstFrame() { 116 | VLog.d(TAG, "onRenderedFirstFrame: ..."); 117 | long playingTime = System.currentTimeMillis(); 118 | long useTime = playingTime - mOnClickTime; 119 | if (useTime < 100000) { 120 | VLog.d(TAG, "播放: use:" + (useTime) + "ms"); 121 | showToast("播放: 耗时:" + (useTime) + "ms"); 122 | } 123 | 124 | } 125 | }; 126 | 127 | private Player.EventListener listener = new Player.EventListener() { 128 | // @Override 129 | // public void onTimelineChanged(Timeline timeline, Object manifest) { 130 | // VLog.d(TAG, "onTimelineChanged: timeline:" + timeline.toString()); 131 | // } 132 | 133 | @Override 134 | public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) { 135 | 136 | } 137 | 138 | @Override 139 | public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { 140 | 141 | } 142 | 143 | @Override 144 | public void onLoadingChanged(boolean isLoading) { 145 | VLog.d(TAG, "onLoadingChanged content:" + mContent); 146 | VLog.d(TAG, "onLoadingChanged: isLoading:" + isLoading); 147 | } 148 | 149 | @Override 150 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { 151 | VLog.d(TAG, "onPlayerStateChanged content:" + mContent); 152 | VLog.d(TAG, "onPlayerStateChanged: playWhenReady:" + playWhenReady + ", playbackState:" + playbackState); 153 | mProgressBar.setVisibility(View.GONE); 154 | if (playbackState == Player.STATE_BUFFERING) { 155 | if (mPlayer != null && mPlayer.isPlaying()) { 156 | mProgressBar.setVisibility(View.VISIBLE); 157 | } 158 | } 159 | 160 | // 开始播放 161 | if (playbackState == Player.STATE_READY) { 162 | 163 | if (playWhenReady) { 164 | mCoverIv.setVisibility(View.GONE); 165 | } 166 | } 167 | 168 | if (playbackState == Player.STATE_ENDED) { 169 | mPlayer.onPlayFinished(); 170 | mStartPlayIv.setImageResource(R.drawable.ic_replay_normal); 171 | mStartPlayIv.setVisibility(View.VISIBLE); 172 | // mCoverIv.setAlpha(1f); 173 | mCoverIv.setVisibility(View.VISIBLE); 174 | } 175 | } 176 | 177 | @Override 178 | public void onRepeatModeChanged(int repeatMode) { 179 | 180 | } 181 | 182 | @Override 183 | public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { 184 | 185 | } 186 | 187 | @Override 188 | public void onPlayerError(ExoPlaybackException error) { 189 | VLog.d(TAG, "onPlayerError content:" + mContent); 190 | VLog.d(TAG, "onPlayerError mId:" + mId); 191 | VLog.e(TAG, "onPlayerError: ", error); 192 | IOException sourceException = error.getSourceException(); 193 | if (sourceException != null && sourceException.getClass().getName().contains("InvalidResponseCodeException")) { 194 | Toast.makeText(mContext, "这个视频403了, 请一会重试", Toast.LENGTH_SHORT).show(); 195 | 196 | } 197 | 198 | } 199 | 200 | @Override 201 | public void onPositionDiscontinuity(int reason) { 202 | VLog.d(TAG, "onLoadingChanged content:" + mContent); 203 | VLog.d(TAG, "onPositionDiscontinuity: reason:" + reason); 204 | } 205 | 206 | @Override 207 | public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { 208 | 209 | } 210 | 211 | @Override 212 | public void onSeekProcessed() { 213 | 214 | } 215 | }; 216 | 217 | private void showToast(final String s) { 218 | mPlayerView.post(new Runnable() { 219 | @Override 220 | public void run() { 221 | Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show(); 222 | } 223 | }); 224 | } 225 | 226 | 227 | private void initViews() { 228 | mProgressBar.setVisibility(View.GONE); 229 | mStartPlayIv.setImageResource(R.drawable.ic_play_video_normal); 230 | mStartPlayIv.setVisibility(View.VISIBLE); 231 | mCoverIv.setAlpha(1f); 232 | mCoverIv.setVisibility(View.VISIBLE); 233 | } 234 | 235 | public void initPlayer(boolean userAction) { 236 | if (!TextUtils.isEmpty(mVideoUrl)) { 237 | 238 | String newUrl = VideoUrlCache.getVideoUrl(mId); 239 | if (!TextUtils.isEmpty(newUrl)) { 240 | mVideoUrl = newUrl; 241 | } 242 | 243 | PlayerManager.getInstance().attchPlayer(mContext, this, mPlayerView, 244 | mVideoUrl, listener, videoListener, userAction); 245 | 246 | } 247 | } 248 | 249 | public void resetUi() { 250 | initViews(); 251 | } 252 | 253 | public void onViewAttachedToWindow() { 254 | VLog.d(TAG, "onViewAttachedToWindow content:" + mContent + ", articleId:" + mId); 255 | PlayerManager.getInstance().preAttch(mVideoUrl, this); 256 | resetUi(); 257 | } 258 | 259 | 260 | public void onViewDetachedFromWindow() { 261 | VLog.d(TAG, "onViewDetachedFromWindow content:" + mContent + ", articleId:" + mId); 262 | VLog.d(TAG, "onViewDetachedFromWindow: ...videoUrl:" + mVideoUrl); 263 | PlayerManager.getInstance().detachPlayer(this, mVideoUrl); 264 | } 265 | 266 | public void release(){ 267 | VLog.d(TAG, "release content:" + mContent + ", articleId:" + mId); 268 | PlayerManager.getInstance().release(mVideoUrl); 269 | } 270 | 271 | @Override 272 | public void onDetached() { 273 | // 跟player分离后, 恢复为默认卡片显示 274 | resetUi(); 275 | } 276 | 277 | @Override 278 | public void preload() { 279 | initPlayer(false); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /app/src/main/java/com/sogo/exoplayer/PlayerManager.java: -------------------------------------------------------------------------------- 1 | package com.sogo.exoplayer; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.google.android.exoplayer2.Player; 7 | import com.google.android.exoplayer2.SimpleExoPlayer; 8 | import com.google.android.exoplayer2.ui.SimpleExoPlayerView; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * Created by shaopengxiang on 2017/12/18. 15 | */ 16 | 17 | public class PlayerManager { 18 | private static final String TAG = "PlayerManager"; 19 | private static final boolean USE_PRELOAD = true; 20 | 21 | private static PlayerManager instance = new PlayerManager(); 22 | 23 | private String sVideoUrl = null; 24 | private PlayerHolder sPlayerHolder = null; 25 | 26 | private Map currentPlayerHolders = new HashMap<>(); 27 | 28 | private PlayerManager() { 29 | } 30 | 31 | public static PlayerManager getInstance() { 32 | return instance; 33 | } 34 | 35 | private static PlayerWrapper player = PlayerWrapper.getInstance(); 36 | 37 | public PlayerWrapper getPlayer(PlayerHolder viewHolder) { 38 | return player; 39 | } 40 | 41 | public void preAttch(final String videoUrl, PlayerHolder playerHolder) { 42 | currentPlayerHolders.put(videoUrl, playerHolder); 43 | if (USE_PRELOAD) { 44 | new Thread(new Runnable() { 45 | @Override 46 | public void run() { 47 | player.preload(videoUrl); 48 | } 49 | }).start(); 50 | 51 | } 52 | } 53 | 54 | public interface PlayerHolder { 55 | void onDetached(); 56 | 57 | void preload(); 58 | } 59 | 60 | public void preload(int pos, String videoUrl) { 61 | if (!VUtil.isWifiConnected(VUtil.getApplication())) { 62 | VLog.d(TAG, "preload[" + pos + "]: NO WIFI NO PRELOAD!" + videoUrl); 63 | return; 64 | } 65 | 66 | VLog.d(TAG, "preload[" + pos + "]: videoUrl:" + videoUrl); 67 | VLog.d(TAG, "preload[" + pos + "]: sVideoUrl:" + sVideoUrl); 68 | VLog.d(TAG, "preload[" + pos + "]: player:" + player); 69 | VLog.d(TAG, "preload[" + pos + "]: player.isPlaying():" + player.isPlaying()); 70 | if (player.isPlaying() && videoUrl.equals(player.getPlayUrl())) { 71 | return; 72 | } 73 | PlayerHolder playerHolder = currentPlayerHolders.get(videoUrl); 74 | VLog.d(TAG, "preload[" + pos + "]: playerHolder:" + playerHolder); 75 | if (playerHolder != null) { 76 | playerHolder.preload(); 77 | } 78 | 79 | } 80 | 81 | // 初始化 exoplayer 82 | public void attchPlayer(Context context, PlayerHolder playerHolder, SimpleExoPlayerView simpleExoPlayerView, 83 | String videoUri, 84 | Player.EventListener eventListener, 85 | SimpleExoPlayer.VideoListener videoListener, 86 | boolean userAction) { 87 | VLog.d(TAG, "attchPlayer videoUrl:" + videoUri + ", player:" + player); 88 | VLog.d(TAG, "attchPlayer player.isReleased():" + player.isReleased()); 89 | if ((sPlayerHolder == playerHolder || videoUri.equals(player.getPlayUrl())) 90 | && !player.isReleased()) { 91 | // if (player.isPlaying()) { 92 | Log.d(TAG, "attchPlayer: return!"); 93 | return; 94 | // } 95 | } 96 | 97 | long start = System.currentTimeMillis(); 98 | if (player != null) { 99 | player.release(); 100 | 101 | if (!videoUri.equals(sVideoUrl) && sPlayerHolder != null && sPlayerHolder != playerHolder) { 102 | sPlayerHolder.onDetached(); 103 | } 104 | } 105 | sPlayerHolder = playerHolder; 106 | sVideoUrl = videoUri; 107 | 108 | player.init(context, simpleExoPlayerView, videoUri, eventListener, videoListener, userAction); 109 | currentPlayerHolders.put(videoUri, playerHolder); 110 | long end = System.currentTimeMillis(); 111 | VLog.d(TAG, "attchPlayer finished! use:" + (end - start) + "ms"); 112 | } 113 | 114 | public void detachPlayer(PlayerHolder playerHolder, String videoUrl) { 115 | VLog.d(TAG, "detachPlayer: videoUrl:" + videoUrl); 116 | if (sPlayerHolder != null 117 | && sPlayerHolder == playerHolder 118 | & videoUrl.equals(sVideoUrl)) { 119 | 120 | VLog.d(TAG, "detachPlayer: will pause player!"); 121 | if (player != null) { 122 | player.pause(); 123 | } 124 | 125 | sPlayerHolder.onDetached(); 126 | sPlayerHolder = null; 127 | sVideoUrl = null; 128 | currentPlayerHolders.remove(videoUrl); 129 | } 130 | } 131 | 132 | public void release(String videoUrl){ 133 | if (player != null) { 134 | player.release(); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/com/sogo/exoplayer/PlayerWrapper.java: -------------------------------------------------------------------------------- 1 | package com.sogo.exoplayer; 2 | 3 | 4 | import android.content.Context; 5 | import android.net.Uri; 6 | 7 | import com.google.android.exoplayer2.DefaultRenderersFactory; 8 | import com.google.android.exoplayer2.ExoPlayer; 9 | import com.google.android.exoplayer2.ExoPlayerFactory; 10 | import com.google.android.exoplayer2.Player; 11 | import com.google.android.exoplayer2.SimpleExoPlayer; 12 | import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; 13 | import com.google.android.exoplayer2.extractor.ExtractorsFactory; 14 | import com.google.android.exoplayer2.source.ExtractorMediaSource; 15 | import com.google.android.exoplayer2.source.MediaSource; 16 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; 17 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; 18 | import com.google.android.exoplayer2.trackselection.TrackSelection; 19 | import com.google.android.exoplayer2.trackselection.TrackSelector; 20 | import com.google.android.exoplayer2.ui.SimpleExoPlayerView; 21 | import com.google.android.exoplayer2.upstream.BandwidthMeter; 22 | import com.google.android.exoplayer2.upstream.DataSource; 23 | import com.google.android.exoplayer2.upstream.DataSpec; 24 | import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 25 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 26 | import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; 27 | import com.google.android.exoplayer2.upstream.cache.CacheUtil; 28 | import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; 29 | import com.google.android.exoplayer2.upstream.cache.SimpleCache; 30 | import com.google.android.exoplayer2.util.Util; 31 | import com.google.android.exoplayer2.video.VideoListener; 32 | import com.spx.exoplayertest.R; 33 | 34 | import java.io.File; 35 | import java.io.IOException; 36 | 37 | 38 | /** 39 | * Manages the {@link ExoPlayer}, the IMA plugin and all video playback. 40 | */ 41 | public final class PlayerWrapper { 42 | 43 | private static final String TAG = "Player.Wrapper"; 44 | private static PlayerWrapper instance = new PlayerWrapper(); 45 | 46 | public static PlayerWrapper getInstance() { 47 | return instance; 48 | } 49 | 50 | // private final ImaAdsLoader adsLoader; 51 | 52 | private SimpleExoPlayer player; 53 | private SimpleExoPlayerView simpleExoPlayerView; 54 | private long contentPosition; 55 | private boolean playFinished = false; 56 | private boolean isPlaying = false; 57 | 58 | private TrackSelector mTrackSelector; 59 | private String mVideoUrl; 60 | private Player.EventListener mEventListener; 61 | private VideoListener mVideoListener; 62 | 63 | 64 | private DataSource.Factory dataSourceFactory = null; 65 | private ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); 66 | 67 | private File cacheFile = null; 68 | 69 | SimpleCache simpleCache = null; 70 | DataSource.Factory cachedDataSourceFactory = null; 71 | // This is the MediaSource representing the content media (i.e. not the ad). 72 | // String contentUrl = context.getString(R.string.content_url); 73 | // MediaSource contentMediaSource = new ExtractorMediaSource( 74 | // Uri.parse(contentUrl), dataSourceFactory, extractorsFactory, null, null); 75 | 76 | // Compose the content media source into a new AdsMediaSource with both ads and content. 77 | // MediaSource mediaSourceWithAds = new AdsMediaSource(contentMediaSource, dataSourceFactory, 78 | // adsLoader, simpleExoPlayerView.getOverlayFrameLayout()); 79 | 80 | 81 | // This is the MediaSource representing the media to be played. 82 | MediaSource videoSource = null; 83 | 84 | private PlayerWrapper() { 85 | dataSourceFactory = new DefaultDataSourceFactory(VUtil.getApplication(), 86 | Util.getUserAgent(VUtil.getApplication(), VUtil.getApplication().getString(R.string.app_name))); 87 | cacheFile = new File(VUtil.getApplication().getExternalCacheDir().getAbsolutePath(), "video"); 88 | VLog.d(TAG, "PlayerWrapper() cache file:" + cacheFile.getAbsolutePath()); 89 | simpleCache = new SimpleCache(cacheFile, new LeastRecentlyUsedCacheEvictor(512 * 1024 * 1024)); 90 | cachedDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory); 91 | BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); 92 | TrackSelection.Factory videoTrackSelectionFactory = 93 | new AdaptiveTrackSelection.Factory(bandwidthMeter); 94 | mTrackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); 95 | 96 | } 97 | 98 | public void preload(String videoUri) { 99 | DataSpec dataSpec = new DataSpec(Uri.parse(videoUri), 0, 512 * 1024, null); 100 | CacheUtil.CachingCounters counters = new CacheUtil.CachingCounters(); 101 | try { 102 | CacheUtil.cache(dataSpec, simpleCache, dataSourceFactory.createDataSource(), counters, null); 103 | } catch (IOException e) { 104 | e.printStackTrace(); 105 | } catch (InterruptedException e) { 106 | e.printStackTrace(); 107 | } 108 | } 109 | 110 | public void init(Context context, SimpleExoPlayerView simpleExoPlayerView, String videoUri, 111 | Player.EventListener eventListener, SimpleExoPlayer.VideoListener videoListener, 112 | boolean userAction) { 113 | this.mVideoUrl = videoUri; 114 | this.mEventListener = eventListener; 115 | this.mVideoListener = videoListener; 116 | initPlayer(context, simpleExoPlayerView, userAction); 117 | } 118 | 119 | private void initPlayer(Context context, SimpleExoPlayerView simpleExoPlayerView, boolean userAction) { 120 | // Create a default track selector. 121 | VLog.d(TAG, "initPlayer: ... " + userAction); 122 | this.simpleExoPlayerView = simpleExoPlayerView; 123 | player = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(context), 124 | mTrackSelector, new LdDefaultLoadControl(userAction)); 125 | 126 | // Bind the player to the view. 127 | simpleExoPlayerView.setPlayer(player); 128 | 129 | // Produces DataSource instances through which media data is loaded. 130 | 131 | // DataSource.Factory dataSourceFactory = new OkHttpDataSourceFactory(); 132 | // Produces Extractor instances for parsing the content media (i.e. not the ad). 133 | 134 | 135 | // Prepare the player with the source. 136 | isPlaying = false; 137 | // player.seekTo(contentPosition); 138 | 139 | new Thread(new Runnable() { 140 | @Override 141 | public void run() { 142 | videoSource = new ExtractorMediaSource(Uri.parse(mVideoUrl), 143 | cachedDataSourceFactory, extractorsFactory, null, null); 144 | player.prepare(videoSource); 145 | } 146 | }).start(); 147 | 148 | // player.setPlayWhenReady(true); 149 | 150 | player.addListener(mEventListener); 151 | player.addVideoListener(mVideoListener); 152 | hasReleased = false; 153 | } 154 | 155 | public void transformIn(SimpleExoPlayerView newSimpleExoPlayerView){ 156 | newSimpleExoPlayerView.setPlayer(player); 157 | simpleExoPlayerView.setPlayer(null); 158 | } 159 | public void transformOut(SimpleExoPlayerView newSimpleExoPlayerView){ 160 | simpleExoPlayerView.setPlayer(player); 161 | newSimpleExoPlayerView.setPlayer(null); 162 | } 163 | 164 | public void onPlayFinished() { 165 | VLog.d(TAG, "onPlayFinished: ..."); 166 | playFinished = true; 167 | isPlaying = false; 168 | } 169 | 170 | public boolean isPlaying() { 171 | return isPlaying; 172 | } 173 | 174 | public void play() { 175 | VLog.d(TAG, "play: ..."); 176 | if (playFinished) { 177 | player.seekTo(0); 178 | playFinished = false; 179 | } 180 | player.setPlayWhenReady(true); 181 | 182 | if (player.getPlaybackState() == Player.STATE_IDLE) { 183 | VLog.d(TAG, "player is IDLE!!!"); 184 | } 185 | 186 | isPlaying = true; 187 | hasReleased = false; 188 | } 189 | 190 | public boolean isIdle() { 191 | if (player != null) { 192 | if (player.getPlaybackState() == Player.STATE_IDLE) { 193 | return true; 194 | } 195 | } 196 | return false; 197 | } 198 | 199 | public void pause() { 200 | VLog.d(TAG, "pause: ..."); 201 | if (player != null) { 202 | isPlaying = false; 203 | player.stop(); 204 | } 205 | } 206 | 207 | private boolean hasReleased = false; 208 | 209 | public void release() { 210 | VLog.d(TAG, "release: ..."+this); 211 | if (player != null) { 212 | isPlaying = false; 213 | player.removeListener(mEventListener); 214 | player.removeVideoListener(mVideoListener); 215 | player.release(); 216 | hasReleased = true; 217 | VLog.d(TAG, "release: ...hasReleased!"); 218 | // player = null; 219 | } 220 | // adsLoader.release(); 221 | } 222 | 223 | public String getPlayUrl() { 224 | return mVideoUrl; 225 | } 226 | 227 | public boolean isReleased() { 228 | return hasReleased; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /app/src/main/java/com/sogo/exoplayer/VLog.java: -------------------------------------------------------------------------------- 1 | package com.sogo.exoplayer; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Created by shaopengxiang on 2017/12/20. 7 | */ 8 | 9 | public class VLog { 10 | public static void d(String tag, String msg) { 11 | Log.d(tag, msg); 12 | } 13 | 14 | public static void d(String tag, String msg, Throwable th) { 15 | Log.d(tag, msg, th); 16 | } 17 | 18 | public static void e(String tag, String msg) { 19 | Log.d(tag, msg); 20 | } 21 | 22 | public static void e(String tag, String msg, Throwable th) { 23 | Log.d(tag, msg, th); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sogo/exoplayer/VUtil.java: -------------------------------------------------------------------------------- 1 | package com.sogo.exoplayer; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.net.ConnectivityManager; 6 | import android.net.NetworkInfo; 7 | 8 | import com.spx.exoplayertest.MyApplication; 9 | 10 | import java.lang.reflect.Field; 11 | 12 | 13 | /** 14 | * Created by shaopengxiang on 2017/12/20. 15 | */ 16 | 17 | public class VUtil { 18 | 19 | /** 20 | * 判断wifi是否连接 21 | * 22 | * @param context 23 | * @return true:wifi连接状态 24 | */ 25 | public static boolean isWifiConnected(Context context) { 26 | ConnectivityManager connManager = (ConnectivityManager) context 27 | .getSystemService(Context.CONNECTIVITY_SERVICE); 28 | NetworkInfo mWifi = connManager 29 | .getNetworkInfo(ConnectivityManager.TYPE_WIFI); 30 | return mWifi.isConnected(); 31 | } 32 | 33 | public static Application getApplication(){ 34 | return MyApplication.getApp(); 35 | } 36 | 37 | // 获得通知栏高度 38 | public static int getStatusBarHeight(Context context) { 39 | Class c = null; 40 | Object obj = null; 41 | Field field = null; 42 | int x = 0, statusBarHeight = 0; 43 | try { 44 | c = Class.forName("com.android.internal.R$dimen"); 45 | obj = c.newInstance(); 46 | field = c.getField("status_bar_height"); 47 | x = Integer.parseInt(field.get(obj).toString()); 48 | statusBarHeight = context.getResources().getDimensionPixelSize(x); 49 | } catch (Exception e1) { 50 | e1.printStackTrace(); 51 | } 52 | return statusBarHeight; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/sogo/exoplayer/VideoUrlCache.java: -------------------------------------------------------------------------------- 1 | package com.sogo.exoplayer; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * @auther shaopx 8 | * @date 2017/12/13. 9 | */ 10 | 11 | public class VideoUrlCache { 12 | 13 | private static Map videoUrls = new HashMap<>(); 14 | 15 | public static void putVideoUrl(int id, String data) { 16 | videoUrls.put(id, data); 17 | } 18 | 19 | public static String getVideoUrl(int id){ 20 | return videoUrls.get(id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/spx/exoplayertest/DraggableLayout.java: -------------------------------------------------------------------------------- 1 | package com.spx.exoplayertest; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.widget.RelativeLayout; 9 | 10 | /** 11 | * Created by shaopengxiang on 2017/12/22. 12 | */ 13 | 14 | public class DraggableLayout extends RelativeLayout { 15 | 16 | private static final String TAG = "DraggableLayout"; 17 | private View mDragView; 18 | 19 | public DraggableLayout(Context context) { 20 | super(context); 21 | } 22 | 23 | public DraggableLayout(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | } 26 | 27 | public DraggableLayout(Context context, AttributeSet attrs, int defStyleAttr) { 28 | super(context, attrs, defStyleAttr); 29 | } 30 | 31 | @Override 32 | protected void onFinishInflate() { 33 | super.onFinishInflate(); 34 | mDragView = findViewById(R.id.player_area); 35 | } 36 | 37 | private float rawX, rawY; 38 | private float last_x, last_y; 39 | 40 | // @Override 41 | // public boolean dispatchTouchEvent(MotionEvent ev) { 42 | // Log.d(TAG, "dispatchTouchEvent: ..."+ev.toString()); 43 | // return super.dispatchTouchEvent(ev); 44 | // } 45 | // 46 | // @Override 47 | // public boolean onInterceptTouchEvent(MotionEvent ev) { 48 | // Log.d(TAG, "onInterceptTouchEvent: ..."); 49 | //// return super.onInterceptTouchEvent(ev); 50 | // return true; 51 | // } 52 | // 53 | // @Override 54 | // public boolean onTouchEvent(MotionEvent event) { 55 | // Log.d(TAG, "onTouchEvent: ..."+event.toString()); 56 | // switch (event.getAction()) { 57 | // case MotionEvent.ACTION_DOWN: 58 | // rawX = event.getX(); 59 | // rawY = event.getY(); 60 | // break; 61 | // case MotionEvent.ACTION_MOVE: 62 | // float touch_x = event.getX(); 63 | // float touch_y = event.getY(); 64 | // mDragView.setTranslationY(touch_y - rawY); 65 | // mDragView.setTranslationX(touch_x - rawX); 66 | // last_y = touch_y; 67 | // last_x = touch_x; 68 | // break; 69 | // case MotionEvent.ACTION_UP: 70 | // break; 71 | // } 72 | // return true; 73 | // } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/spx/exoplayertest/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.spx.exoplayertest; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.TextView; 12 | 13 | import com.sogo.exoplayer.LdExoPlayerController; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | /** 19 | * 20 | */ 21 | public class MainActivity extends AppCompatActivity { 22 | private String[] sources = new String[]{ 23 | "http://video.wenwen.sogou.com/e75aa445e10f4a51cf9b1df72b947e40.mp4.f20.mp4", 24 | "http://video.wenwen.sogou.com/8640cc39cd2abaa644dbc9f4eef4b49e.mp4.f20.mp4", 25 | "http://video.wenwen.sogou.com/31bc728f5bd22e0cf6aa4898f7f78f0d.mp4.f20.mp4", 26 | "http://video.wenwen.sogou.com/8cd0ffa1bea136eef0b42057f305b7b0.mp4.f20.mp4", 27 | "http://video.wenwen.sogou.com/4dfd051e27a32ae319588e7066937398.mp4.f20.mp4", 28 | }; 29 | 30 | private RecyclerView mRecyclerView; 31 | private Adapter mAdapter; 32 | 33 | private List mUrls = Arrays.asList(sources); 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_main); 39 | 40 | mRecyclerView = findViewById(R.id.recyclerView); 41 | mAdapter = new Adapter(); 42 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 43 | mRecyclerView.setAdapter(mAdapter); 44 | 45 | 46 | } 47 | 48 | @Override 49 | protected void onResume() { 50 | super.onResume(); 51 | } 52 | 53 | @Override 54 | protected void onPause() { 55 | super.onPause(); 56 | } 57 | 58 | private class ViewHolder extends RecyclerView.ViewHolder { 59 | 60 | private LdExoPlayerController controller; 61 | private TextView titleTv; 62 | private View playView; 63 | 64 | public ViewHolder(final View itemView) { 65 | super(itemView); 66 | controller = new LdExoPlayerController(itemView); 67 | titleTv = itemView.findViewById(R.id.title_tv); 68 | playView = itemView.findViewById(R.id.player_view); 69 | 70 | 71 | itemView.setOnClickListener(new View.OnClickListener() { 72 | @Override 73 | public void onClick(View v) { 74 | int adapterPosition = getAdapterPosition(); 75 | String url = mUrls.get(adapterPosition); 76 | Intent intent = new Intent(itemView.getContext(), VideoPlayActivity.class); 77 | intent.putExtra("play_url", url); 78 | intent.putExtra("play_id", adapterPosition); 79 | int[] location = new int[2]; 80 | playView.getLocationOnScreen(location); 81 | int width = playView.getWidth(); 82 | int height = playView.getHeight(); 83 | intent.putExtra("view_x", location[0]); 84 | intent.putExtra("view_y", location[1]); 85 | intent.putExtra("view_w", width); 86 | intent.putExtra("view_h", height); 87 | 88 | itemView.getContext().startActivity(intent); 89 | itemView.postDelayed(new Runnable() { 90 | @Override 91 | public void run() { 92 | controller.onDetached(); 93 | } 94 | }, 300); 95 | } 96 | }); 97 | } 98 | 99 | public void bindData(int position) { 100 | titleTv.setText("这是测试数据:[" + position + "] url:" + mUrls.get(position)); 101 | 102 | controller.bindData(itemView.getContext(), position, 103 | "position_" + position, mUrls.get(position), ""); 104 | } 105 | } 106 | 107 | private class Adapter extends RecyclerView.Adapter { 108 | 109 | @Override 110 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 111 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 112 | View view = inflater.inflate(R.layout.list_item_layout, parent, false); 113 | return new ViewHolder(view); 114 | } 115 | 116 | @Override 117 | public void onBindViewHolder(ViewHolder holder, int position) { 118 | holder.bindData(position); 119 | } 120 | 121 | @Override 122 | public int getItemCount() { 123 | return mUrls.size(); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/spx/exoplayertest/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.spx.exoplayertest; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * Created by shaopengxiang on 2017/12/20. 7 | */ 8 | 9 | public class MyApplication extends Application { 10 | private static MyApplication sApplication = null; 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | sApplication = this; 15 | } 16 | 17 | public static MyApplication getApp(){ 18 | return sApplication; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/spx/exoplayertest/VideoPlayActivity.java: -------------------------------------------------------------------------------- 1 | package com.spx.exoplayertest; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.PropertyValuesHolder; 6 | import android.animation.ValueAnimator; 7 | import android.app.Activity; 8 | import android.graphics.Color; 9 | import android.os.Bundle; 10 | import android.util.DisplayMetrics; 11 | import android.util.Log; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.animation.AccelerateDecelerateInterpolator; 16 | 17 | import com.sogo.exoplayer.LdExoPlayerController; 18 | import com.sogo.exoplayer.VUtil; 19 | 20 | public class VideoPlayActivity extends Activity { 21 | private static final String TAG = "VideoPlayActivity"; 22 | private static final long ANIMATION_DURATION = 300; 23 | 24 | private int mVideoId; 25 | private String mVideoUrl = null; 26 | private View playView; 27 | private LdExoPlayerController controller; 28 | private View mRootView; 29 | 30 | private int viewX, viewY, viewW, viewH; 31 | private ValueAnimator animatorStart = new ValueAnimator(); 32 | private ValueAnimator animatorEnd = new ValueAnimator(); 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_video_play); 38 | 39 | mRootView = findViewById(R.id.rootview); 40 | // mRootView.setOnTouchListener(new View.OnTouchListener() { 41 | // @Override 42 | // public boolean onTouch(View v, MotionEvent event) { 43 | // Log.d(TAG, "onTouch: ..."+event.toString()); 44 | // return true; 45 | // } 46 | // }); 47 | 48 | mVideoId = getIntent().getIntExtra("play_id", 0); 49 | mVideoUrl = getIntent().getStringExtra("play_url"); 50 | 51 | viewX = getIntent().getIntExtra("view_x", 0); 52 | viewY = getIntent().getIntExtra("view_y", 0); 53 | viewW = getIntent().getIntExtra("view_w", 0); 54 | viewH = getIntent().getIntExtra("view_h", 0); 55 | 56 | Log.d(TAG, "onCreate: url:" + mVideoUrl); 57 | Log.d(TAG, "onCreate: viewX:" + viewX + ", viewY:" + viewY + ", viewW:" + viewW + ", viewH:" + viewH); 58 | 59 | playView = findViewById(R.id.player_area); 60 | controller = new LdExoPlayerController(playView); 61 | controller.bindData(this, mVideoId, "", mVideoUrl, ""); 62 | controller.initPlayer(false); 63 | 64 | controller.transformIn(mVideoUrl); 65 | 66 | initTransform(); 67 | transformIn(); 68 | } 69 | 70 | private void transformIn() { 71 | animatorStart.start(); 72 | } 73 | 74 | private void transformOut() { 75 | animatorEnd.start(); 76 | } 77 | 78 | private void initTransform() { 79 | DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 80 | int widthPixels = displayMetrics.widthPixels; 81 | int heightPixels = displayMetrics.heightPixels; 82 | 83 | int playerViewHeight = getResources().getDimensionPixelSize(R.dimen.player_view_height); 84 | int statusBarHeight = VUtil.getStatusBarHeight(this); 85 | Log.d(TAG, "onCreate: statusBarHeight:" + statusBarHeight); 86 | viewY = viewY - statusBarHeight; 87 | 88 | { 89 | animatorStart.setDuration(ANIMATION_DURATION); 90 | animatorStart.setInterpolator(new AccelerateDecelerateInterpolator()); 91 | 92 | PropertyValuesHolder wHolder = PropertyValuesHolder.ofInt("width", viewW, widthPixels); 93 | PropertyValuesHolder hHolder = PropertyValuesHolder.ofInt("height", viewH, playerViewHeight); 94 | PropertyValuesHolder xHolder = PropertyValuesHolder.ofFloat("transactionX", viewX, 0); 95 | PropertyValuesHolder yHolder = PropertyValuesHolder.ofFloat("transactionY", viewY, 0); 96 | animatorStart.setValues(wHolder, hHolder, xHolder, yHolder); 97 | 98 | animatorStart.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 99 | @Override 100 | public void onAnimationUpdate(ValueAnimator animation) { 101 | int width = (Integer) animation.getAnimatedValue("width"); 102 | int height = (Integer) animation.getAnimatedValue("height"); 103 | float transactionX = (float) animation.getAnimatedValue("transactionX"); 104 | float transactionY = (float) animation.getAnimatedValue("transactionY"); 105 | 106 | ViewGroup.LayoutParams layoutParams = playView.getLayoutParams(); 107 | layoutParams.width = width; 108 | layoutParams.height = height; 109 | playView.setLayoutParams(layoutParams); 110 | 111 | playView.setTranslationX(transactionX); 112 | playView.setTranslationY(transactionY); 113 | 114 | float animatedFraction = animation.getAnimatedFraction(); 115 | float alpha = animatedFraction * 0.8f; 116 | Log.d(TAG, "onAnimationUpdate: alpha:" + alpha); 117 | mRootView.setBackgroundColor(getColorWithAlpha(alpha, Color.BLACK)); 118 | } 119 | }); 120 | } 121 | 122 | 123 | { 124 | animatorEnd.setDuration(ANIMATION_DURATION); 125 | animatorEnd.setInterpolator(new AccelerateDecelerateInterpolator()); 126 | 127 | PropertyValuesHolder wHolder = PropertyValuesHolder.ofInt("width", widthPixels, viewW); 128 | PropertyValuesHolder hHolder = PropertyValuesHolder.ofInt("height", playerViewHeight, viewH); 129 | PropertyValuesHolder xHolder = PropertyValuesHolder.ofFloat("transactionX", 0, viewX); 130 | PropertyValuesHolder yHolder = PropertyValuesHolder.ofFloat("transactionY", 0, viewY); 131 | animatorEnd.setValues(wHolder, hHolder, xHolder, yHolder); 132 | 133 | animatorEnd.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 134 | @Override 135 | public void onAnimationUpdate(ValueAnimator animation) { 136 | int width = (Integer) animation.getAnimatedValue("width"); 137 | int height = (Integer) animation.getAnimatedValue("height"); 138 | float transactionX = (float) animation.getAnimatedValue("transactionX"); 139 | float transactionY = (float) animation.getAnimatedValue("transactionY"); 140 | 141 | ViewGroup.LayoutParams layoutParams = playView.getLayoutParams(); 142 | layoutParams.width = width; 143 | layoutParams.height = height; 144 | playView.setLayoutParams(layoutParams); 145 | 146 | playView.setTranslationX(transactionX); 147 | playView.setTranslationY(transactionY); 148 | 149 | float animatedFraction = animation.getAnimatedFraction(); 150 | float alpha = (1 - animatedFraction) * 0.8f; 151 | Log.d(TAG, "onAnimationUpdate: alpha:" + alpha); 152 | mRootView.setBackgroundColor(getColorWithAlpha(alpha, Color.BLACK)); 153 | } 154 | }); 155 | 156 | animatorEnd.addListener(new AnimatorListenerAdapter() { 157 | @Override 158 | public void onAnimationEnd(Animator animation) { 159 | exit(); 160 | } 161 | }); 162 | } 163 | 164 | } 165 | 166 | @Override 167 | protected void onResume() { 168 | super.onResume(); 169 | controller.onViewAttachedToWindow(); 170 | } 171 | 172 | @Override 173 | protected void onPause() { 174 | super.onPause(); 175 | controller.onViewDetachedFromWindow(); 176 | } 177 | 178 | @Override 179 | public void onBackPressed() { 180 | transformOut(); 181 | } 182 | 183 | @Override 184 | protected void onDestroy() { 185 | super.onDestroy(); 186 | controller.release(); 187 | } 188 | 189 | /** 190 | * 关闭页面 191 | */ 192 | public void exit() { 193 | controller.transformOut(); 194 | finish(); 195 | overridePendingTransition(0, 0); 196 | } 197 | 198 | @Override 199 | public void onWindowFocusChanged(boolean hasFocus) { 200 | super.onWindowFocusChanged(hasFocus); 201 | // if (hasFocus && Build.VERSION.SDK_INT >= 19) { 202 | // View decorView = getWindow().getDecorView(); 203 | // decorView.setSystemUiVisibility( 204 | // View.SYSTEM_UI_FLAG_LAYOUT_STABLE 205 | // | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 206 | // | View.SYSTEM_UI_FLAG_FULLSCREEN 207 | // | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 208 | // } 209 | } 210 | 211 | public static int getColorWithAlpha(float alpha, int baseColor) { 212 | int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24; 213 | int rgb = 0x00ffffff & baseColor; 214 | return a + rgb; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /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-xhdpi/ic_play_video_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/drawable-xhdpi/ic_play_video_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_replay_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/drawable-xhdpi/ic_replay_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/sg_player_player_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/drawable-xhdpi/sg_player_player_btn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/sg_seek_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/drawable-xhdpi/sg_seek_dot.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/sg_stop_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/drawable-xhdpi/sg_stop_btn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_video_control.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_video_play.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/exo_playback_control_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 22 | 23 | 33 | 34 | 42 | 43 | 44 | 53 | 54 | 63 | 64 | 73 | 74 | 75 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/video_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 33 | 34 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaopx/ExoplayerTest/23e61519f7097a26b15ff8861093e502bd3562c6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-v19/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 220dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ExoplayerTest 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/test/java/com/spx/exoplayertest/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.spx.exoplayertest; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.0.1' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 17 10 | targetSdkVersion 26 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:26.1.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 34 | } 35 | -------------------------------------------------------------------------------- /core/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 | -------------------------------------------------------------------------------- /core/src/androidTest/java/com/spx/core/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.spx.core; 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.spx.core.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /core/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | core 3 | 4 | -------------------------------------------------------------------------------- /core/src/test/java/com/spx/core/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.spx.core; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':core' 2 | --------------------------------------------------------------------------------