Commit f97881e6a78a1a8f75f097a3f2836732a4a6b713
1 parent
c39ca37a
更新推流SDK, 添加悬浮窗预览、截屏、背景图推流、音频滤镜、滤镜等功能
Showing
100 changed files
with
1574 additions
and
124 deletions
Too many changes to show.
To preserve performance only 100 of 104 files are displayed.
app/build.gradle
| ... | ... | @@ -43,7 +43,11 @@ repositories { |
| 43 | 43 | |
| 44 | 44 | dependencies { |
| 45 | 45 | compile fileTree(include: ['*.jar'], dir: 'libs') |
| 46 | + compile 'com.cnlive:lib_cnlive:2.1.0' | |
| 46 | 47 | compile 'com.android.support:appcompat-v7:25.2.0' |
| 47 | 48 | compile 'com.android.support.constraint:constraint-layout:1.0.1' |
| 48 | - compile 'com.cnlive:lib_cnlive:2.0.0' | |
| 49 | + | |
| 50 | + compile 'com.facebook.fresco:fresco:1.4.0' | |
| 51 | + compile 'com.facebook.fresco:animated-gif:1.4.0' | |
| 52 | + compile 'com.facebook.fresco:animated-webp:1.4.0' | |
| 49 | 53 | } | ... | ... |
app/src/main/AndroidManifest.xml
| ... | ... | @@ -35,7 +35,8 @@ |
| 35 | 35 | android:name=".StreamActivity" |
| 36 | 36 | android:configChanges="orientation|keyboardHidden|screenSize" |
| 37 | 37 | android:launchMode="singleTop" |
| 38 | - android:screenOrientation="portrait" /> | |
| 38 | + android:screenOrientation="portrait" | |
| 39 | + android:theme="@style/Theme.RiseAnimation" /> | |
| 39 | 40 | <activity |
| 40 | 41 | android:name=".CNStreamActivity" |
| 41 | 42 | android:configChanges="orientation|keyboardHidden|screenSize" |
| ... | ... | @@ -46,6 +47,11 @@ |
| 46 | 47 | android:configChanges="orientation|keyboardHidden|screenSize" |
| 47 | 48 | android:launchMode="singleTop" |
| 48 | 49 | android:screenOrientation="portrait" /> |
| 50 | + <activity | |
| 51 | + android:name=".FloatActivity" | |
| 52 | + android:configChanges="orientation|keyboardHidden|screenSize" | |
| 53 | + android:launchMode="singleTop" | |
| 54 | + android:theme="@style/Theme.RiseAnimation"></activity> | |
| 49 | 55 | </application> |
| 50 | 56 | |
| 51 | 57 | </manifest> |
| 52 | 58 | \ No newline at end of file | ... | ... |
app/src/main/assets/KSYResource/0_pink.png
0 → 100644
158 KB
app/src/main/assets/KSYResource/0_ruddy2.png
0 → 100644
131 KB
app/src/main/assets/KSYResource/10_sunshine_night.png
0 → 100644
197 KB
app/src/main/assets/KSYResource/11_ruddy.png
0 → 100644
132 KB
app/src/main/assets/KSYResource/12_sunshine.png
0 → 100644
204 KB
app/src/main/assets/KSYResource/13_nature.png
0 → 100644
771 KB
app/src/main/assets/KSYResource/14_amatorka.png
0 → 100644
35.5 KB
app/src/main/assets/KSYResource/15_elegance_1.png
0 → 100644
126 KB
app/src/main/assets/KSYResource/1_freshy.png
0 → 100644
196 KB
app/src/main/assets/KSYResource/2_beauty.png
0 → 100644
64.4 KB
app/src/main/assets/KSYResource/3_sweety.png
0 → 100644
50.3 KB
app/src/main/assets/KSYResource/4_sepia.png
0 → 100644
35.5 KB
app/src/main/assets/KSYResource/5_blue.png
0 → 100644
198 KB
app/src/main/assets/KSYResource/6_nostalgia.png
0 → 100644
136 KB
app/src/main/assets/KSYResource/7_sakura.png
0 → 100644
241 KB
app/src/main/assets/KSYResource/8_sakura_night.png
0 → 100644
226 KB
app/src/main/assets/KSYResource/9_ruddy_night.png
0 → 100644
125 KB
app/src/main/assets/KSYResource/amaro_map.png
0 → 100644
2.95 KB
app/src/main/assets/KSYResource/blackboard_1024.png
0 → 100644
103 KB
app/src/main/assets/KSYResource/blowout_1977.png
0 → 100644
6.62 KB
app/src/main/assets/KSYResource/brannan_blowout.png
0 → 100644
174 Bytes
app/src/main/assets/KSYResource/brannan_contrast.png
0 → 100644
199 Bytes
app/src/main/assets/KSYResource/brannan_luma.png
0 → 100644
229 Bytes
app/src/main/assets/KSYResource/brannan_process.png
0 → 100644
285 Bytes
app/src/main/assets/KSYResource/brannan_screen.png
0 → 100644
217 Bytes
app/src/main/assets/KSYResource/early_bird_curves.png
0 → 100644
156 Bytes
app/src/main/assets/KSYResource/earlybird_blowout.png
0 → 100644
159 Bytes
app/src/main/assets/KSYResource/earlybird_map.png
0 → 100644
208 Bytes
app/src/main/assets/KSYResource/earlybird_overlay_map.png
0 → 100644
187 Bytes
app/src/main/assets/KSYResource/edge_burn.png
0 → 100644
30.5 KB
app/src/main/assets/KSYResource/hefe_gradient_map.png
0 → 100644
2.85 KB
app/src/main/assets/KSYResource/hefe_map.png
0 → 100644
2.93 KB
app/src/main/assets/KSYResource/hefe_metal.png
0 → 100644
109 KB
app/src/main/assets/KSYResource/hefe_soft_light.png
0 → 100644
5.63 KB
app/src/main/assets/KSYResource/hudson_background.png
0 → 100644
85.1 KB
app/src/main/assets/KSYResource/hudson_map.png
0 → 100644
2.82 KB
app/src/main/assets/KSYResource/inkwell_map.png
0 → 100644
2.8 KB
app/src/main/assets/KSYResource/kelvin_map.png
0 → 100644
2.94 KB
app/src/main/assets/KSYResource/lomo_map.png
0 → 100644
2.83 KB
app/src/main/assets/KSYResource/map_1977.png
0 → 100644
279 Bytes
app/src/main/assets/KSYResource/nashville_map.png
0 → 100644
2.95 KB
app/src/main/assets/KSYResource/nashwi.png
0 → 100644
2.95 KB
app/src/main/assets/KSYResource/overlay_map.png
0 → 100644
6.3 KB
app/src/main/assets/KSYResource/rise_map.png
0 → 100644
2.98 KB
app/src/main/assets/KSYResource/sierra_map.png
0 → 100644
237 Bytes
app/src/main/assets/KSYResource/sierra_vignette.png
0 → 100644
69.9 KB
app/src/main/assets/KSYResource/soft_light.png
0 → 100644
5.69 KB
app/src/main/assets/KSYResource/sutro_curves.png
0 → 100644
2.84 KB
app/src/main/assets/KSYResource/sutro_edge_burn.png
0 → 100644
91.8 KB
app/src/main/assets/KSYResource/sutro_metal.png
0 → 100644
89.4 KB
app/src/main/assets/KSYResource/toaster_color_shift.png
0 → 100644
322 Bytes
app/src/main/assets/KSYResource/toaster_curves.png
0 → 100644
297 Bytes
app/src/main/assets/KSYResource/toaster_metal.png
0 → 100644
104 KB
app/src/main/assets/KSYResource/toaster_overlay_map_warm.png
0 → 100644
22.5 KB
app/src/main/assets/KSYResource/toaster_soft_light.png
0 → 100644
4.24 KB
app/src/main/assets/KSYResource/valencia_gradient_map.png
0 → 100644
12.4 KB
app/src/main/assets/KSYResource/valencia_map.png
0 → 100644
2.91 KB
app/src/main/assets/KSYResource/vignette_map.png
0 → 100644
7.51 KB
app/src/main/assets/KSYResource/walden_map.png
0 → 100644
3 KB
app/src/main/assets/KSYResource/xpro_map.png
0 → 100644
236 Bytes
app/src/main/assets/bg.jpg
0 → 100644
126 KB
app/src/main/assets/cnlive_logo.gif
0 → 100644
111 KB
app/src/main/java/com/cnlive/demo/stream/AnimatedImageCapture.java
0 → 100644
| 1 | +package com.cnlive.demo.stream; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.graphics.Bitmap; | |
| 5 | +import android.graphics.Canvas; | |
| 6 | +import android.graphics.Color; | |
| 7 | +import android.graphics.Rect; | |
| 8 | +import android.net.Uri; | |
| 9 | +import android.util.Log; | |
| 10 | + | |
| 11 | +import com.cnlive.libs.stream.Streamer; | |
| 12 | +import com.cnlive.libs.stream.capture.CNImgTexSrcPin; | |
| 13 | +import com.facebook.common.executors.CallerThreadExecutor; | |
| 14 | +import com.facebook.common.references.CloseableReference; | |
| 15 | +import com.facebook.datasource.BaseDataSubscriber; | |
| 16 | +import com.facebook.datasource.DataSource; | |
| 17 | +import com.facebook.datasource.DataSubscriber; | |
| 18 | +import com.facebook.drawee.backends.pipeline.Fresco; | |
| 19 | +import com.facebook.imagepipeline.animated.base.AnimatedDrawableFrameInfo; | |
| 20 | +import com.facebook.imagepipeline.animated.base.AnimatedImage; | |
| 21 | +import com.facebook.imagepipeline.animated.base.AnimatedImageFrame; | |
| 22 | +import com.facebook.imagepipeline.core.ImagePipeline; | |
| 23 | +import com.facebook.imagepipeline.image.CloseableAnimatedImage; | |
| 24 | +import com.facebook.imagepipeline.image.CloseableBitmap; | |
| 25 | +import com.facebook.imagepipeline.image.CloseableImage; | |
| 26 | +import com.facebook.imagepipeline.request.ImageRequest; | |
| 27 | +import com.facebook.imagepipeline.request.ImageRequestBuilder; | |
| 28 | + | |
| 29 | +import java.util.Date; | |
| 30 | +import java.util.Timer; | |
| 31 | +import java.util.TimerTask; | |
| 32 | +import java.util.stream.Stream; | |
| 33 | + | |
| 34 | +/** | |
| 35 | + * Created by Lynn on 2017/9/19. | |
| 36 | + */ | |
| 37 | + | |
| 38 | +public class AnimatedImageCapture { | |
| 39 | + private static final String TAG = "AnimatedImageCapture"; | |
| 40 | + private static final boolean VERBOSE = false; | |
| 41 | + | |
| 42 | + private int mIndex; | |
| 43 | + private Date mLastDate; | |
| 44 | + private int mRepeatCount; // TODO: handle gif/webp repeat count | |
| 45 | + private Bitmap mBitmap; | |
| 46 | + private Canvas mCanvas; | |
| 47 | + private Bitmap mTempBitmap; | |
| 48 | + private Timer mTimer; | |
| 49 | + private CloseableReference<? extends CloseableImage> mCloseableReference; | |
| 50 | + | |
| 51 | + private Streamer mStreamer; | |
| 52 | + | |
| 53 | + private CNImgTexSrcPin cnImgTexSrcPin; | |
| 54 | + private Context context; | |
| 55 | + | |
| 56 | + public AnimatedImageCapture(Context context, Streamer streamer) { | |
| 57 | + this.context = context; | |
| 58 | + this.mStreamer = streamer; | |
| 59 | + cnImgTexSrcPin = new CNImgTexSrcPin(streamer.getGLRender()); | |
| 60 | + } | |
| 61 | + | |
| 62 | + public void showAnimatedWaterMard(String url, float x, float y, float w, float h, float a) { | |
| 63 | + cnImgTexSrcPin.connect(mStreamer.getImgTexPreviewMixer().getSinkPin(3)); | |
| 64 | + cnImgTexSrcPin.connect(mStreamer.getImgTexMixer().getSinkPin(3)); | |
| 65 | + mStreamer.getImgTexPreviewMixer().setRenderRect(3, x, y, w, h, a); | |
| 66 | + mStreamer.getImgTexMixer().setRenderRect(3, x, y, w, h, a); | |
| 67 | + start(context, url); | |
| 68 | + } | |
| 69 | + | |
| 70 | + public void hideAnimatedWaterMark() { | |
| 71 | + stop(); | |
| 72 | + cnImgTexSrcPin.disconnect(false); | |
| 73 | + } | |
| 74 | + | |
| 75 | + public void start(Context context, String url) { | |
| 76 | + if (url == null) { | |
| 77 | + return; | |
| 78 | + } | |
| 79 | + | |
| 80 | + Uri uri = Uri.parse(assets2Asset(url)); | |
| 81 | + ImagePipeline imagePipeline = Fresco.getImagePipeline(); | |
| 82 | + ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).build(); | |
| 83 | + DataSource<CloseableReference<CloseableImage>> dataSource = | |
| 84 | + imagePipeline.fetchDecodedImage(request, context); | |
| 85 | + dataSource.subscribe(mDataSubscriber, CallerThreadExecutor.getInstance()); | |
| 86 | + } | |
| 87 | + | |
| 88 | + private String assets2Asset(String url) { | |
| 89 | + if (url.startsWith("assets://")) { | |
| 90 | + url = url.replaceFirst("assets://", "asset:///"); | |
| 91 | + } | |
| 92 | + return url; | |
| 93 | + } | |
| 94 | + | |
| 95 | + public void stop() { | |
| 96 | + if (mTimer != null) { | |
| 97 | + mTimer.cancel(); | |
| 98 | + mTimer = null; | |
| 99 | + } | |
| 100 | + if (mCloseableReference != null) { | |
| 101 | + CloseableReference.closeSafely(mCloseableReference); | |
| 102 | + mCloseableReference = null; | |
| 103 | + } | |
| 104 | + cnImgTexSrcPin.updateFrame(null, false); | |
| 105 | + } | |
| 106 | + | |
| 107 | + private void updateFrame() { | |
| 108 | + if (mCloseableReference == null) { | |
| 109 | + return; | |
| 110 | + } | |
| 111 | + try { | |
| 112 | + CloseableImage closeableImage = mCloseableReference.get(); | |
| 113 | + if (closeableImage instanceof CloseableBitmap) { | |
| 114 | + Bitmap bitmap = ((CloseableBitmap) closeableImage).getUnderlyingBitmap(); | |
| 115 | + if (bitmap != null && !bitmap.isRecycled()) { | |
| 116 | + mBitmap = Bitmap.createBitmap(bitmap); | |
| 117 | + cnImgTexSrcPin.updateFrame(mBitmap, false); | |
| 118 | + } | |
| 119 | + return; | |
| 120 | + } | |
| 121 | + | |
| 122 | + if (!(closeableImage instanceof CloseableAnimatedImage)) { | |
| 123 | + return; | |
| 124 | + } | |
| 125 | + AnimatedImage animatedImage = ((CloseableAnimatedImage) closeableImage).getImage(); | |
| 126 | + int w = animatedImage.getWidth(); | |
| 127 | + int h = animatedImage.getHeight(); | |
| 128 | + if (mBitmap == null || mBitmap.isRecycled()) { | |
| 129 | + Log.d(TAG, "Got animated image " + w + "x" + h); | |
| 130 | + mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); | |
| 131 | + mTempBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); | |
| 132 | + mCanvas = new Canvas(mBitmap); | |
| 133 | + } | |
| 134 | + if (mIndex >= animatedImage.getFrameCount()) { | |
| 135 | + mRepeatCount++; | |
| 136 | + mIndex = 0; | |
| 137 | + int loopCount = animatedImage.getLoopCount(); | |
| 138 | + if (loopCount > 0 && mRepeatCount >= loopCount) { | |
| 139 | + Log.d(TAG, "repeat ended, repeat times: " + mRepeatCount); | |
| 140 | + return; | |
| 141 | + } | |
| 142 | + } | |
| 143 | + | |
| 144 | + AnimatedImageFrame imageFrame = animatedImage.getFrame(mIndex); | |
| 145 | + int duration = imageFrame.getDurationMs(); | |
| 146 | + AnimatedDrawableFrameInfo frameInfo = animatedImage.getFrameInfo(mIndex); | |
| 147 | + imageFrame.renderFrame(frameInfo.width, frameInfo.height, mTempBitmap); | |
| 148 | + if (VERBOSE) { | |
| 149 | + Log.d(TAG, "blend: " + frameInfo.blendOperation.name() + | |
| 150 | + " dispose: " + frameInfo.disposalMethod.name() + | |
| 151 | + " x=" + frameInfo.xOffset + " y=" + frameInfo.yOffset + | |
| 152 | + " " + frameInfo.width + "x" + frameInfo.height); | |
| 153 | + } | |
| 154 | + if (frameInfo.blendOperation == AnimatedDrawableFrameInfo.BlendOperation.NO_BLEND) { | |
| 155 | + mBitmap.eraseColor(Color.TRANSPARENT); | |
| 156 | + } | |
| 157 | + Rect srcRect = new Rect(0, 0, frameInfo.width, frameInfo.height); | |
| 158 | + Rect dstRect = new Rect(frameInfo.xOffset, frameInfo.yOffset, | |
| 159 | + frameInfo.xOffset + frameInfo.width, frameInfo.yOffset + frameInfo.height); | |
| 160 | + mCanvas.drawBitmap(mTempBitmap, srcRect, dstRect, null); | |
| 161 | + if (VERBOSE) { | |
| 162 | + Log.d(TAG, "frame " + mIndex + " got, duration=" + duration); | |
| 163 | + } | |
| 164 | + cnImgTexSrcPin.updateFrame(mBitmap, false); | |
| 165 | + imageFrame.dispose(); | |
| 166 | + | |
| 167 | + mIndex++; | |
| 168 | + if (mLastDate == null) { | |
| 169 | + mLastDate = new Date(); | |
| 170 | + } | |
| 171 | + mLastDate = new Date(mLastDate.getTime() + duration); | |
| 172 | + mTimer.schedule(new TimerTask() { | |
| 173 | + @Override | |
| 174 | + public void run() { | |
| 175 | + updateFrame(); | |
| 176 | + } | |
| 177 | + }, mLastDate); | |
| 178 | + } catch (Exception e) { | |
| 179 | + e.printStackTrace(); | |
| 180 | + } | |
| 181 | + } | |
| 182 | + | |
| 183 | + private DataSubscriber mDataSubscriber = | |
| 184 | + new BaseDataSubscriber<CloseableReference<? extends CloseableImage>>() { | |
| 185 | + | |
| 186 | + @Override | |
| 187 | + public void onNewResultImpl( | |
| 188 | + DataSource<CloseableReference<? extends CloseableImage>> dataSource) { | |
| 189 | + if (!dataSource.isFinished()) { | |
| 190 | + return; | |
| 191 | + } | |
| 192 | + | |
| 193 | + // get ref | |
| 194 | + mCloseableReference = dataSource.getResult(); | |
| 195 | + | |
| 196 | + // reset | |
| 197 | + mIndex = 0; | |
| 198 | + mRepeatCount = 0; | |
| 199 | + mLastDate = null; | |
| 200 | + mBitmap = null; | |
| 201 | + mTempBitmap = null; | |
| 202 | + mCanvas = null; | |
| 203 | + | |
| 204 | + // create timer | |
| 205 | + mTimer = new Timer("AnimatedImage"); | |
| 206 | + mTimer.schedule(new TimerTask() { | |
| 207 | + @Override | |
| 208 | + public void run() { | |
| 209 | + updateFrame(); | |
| 210 | + } | |
| 211 | + }, 0); | |
| 212 | + } | |
| 213 | + | |
| 214 | + @Override | |
| 215 | + public void onFailureImpl(DataSource dataSource) { | |
| 216 | + Throwable throwable = dataSource.getFailureCause(); | |
| 217 | + // handle failure | |
| 218 | + if (throwable != null) { | |
| 219 | + throwable.printStackTrace(); | |
| 220 | + } | |
| 221 | + } | |
| 222 | + }; | |
| 223 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/stream/AudioActivity.java
| ... | ... | @@ -15,9 +15,15 @@ import android.widget.Toast; |
| 15 | 15 | |
| 16 | 16 | import com.cnlive.libs.stream.Streamer; |
| 17 | 17 | import com.cnlive.libs.stream.base.IStreamer; |
| 18 | +import com.cnlive.libs.stream.filter.CNAudioFilterBase; | |
| 19 | +import com.cnlive.libs.stream.filter.CNAudioReverbFilter; | |
| 18 | 20 | import com.cnlive.libs.stream.model.ActivityConfig; |
| 19 | 21 | |
| 20 | 22 | |
| 23 | +import java.util.LinkedList; | |
| 24 | +import java.util.List; | |
| 25 | + | |
| 26 | +import static com.cnlive.libs.stream.base.IStreamer.AUDIO_REVERB_LEVEL_1; | |
| 21 | 27 | import static com.cnlive.libs.stream.base.IStreamer.ENCODE_METHOD_HARDWARE; |
| 22 | 28 | import static com.cnlive.libs.stream.base.IStreamer.ENCODE_METHOD_SOFTWARE_COMPAT; |
| 23 | 29 | import static com.cnlive.libs.stream.base.IStreamer.VIDEO_RESOLUTION_360P; |
| ... | ... | @@ -434,18 +440,30 @@ public class AudioActivity extends AppCompatActivity implements View.OnClickList |
| 434 | 440 | }).setPositiveButton("确定", new DialogInterface.OnClickListener() { |
| 435 | 441 | @Override |
| 436 | 442 | public void onClick(DialogInterface dialogInterface, int which) { |
| 443 | + List<CNAudioFilterBase> filterBases = new LinkedList<CNAudioFilterBase>(); | |
| 437 | 444 | switch (which) { |
| 438 | 445 | case 0: |
| 439 | - mStreamer.setAudioFilter(IStreamer.AUDIO_REVERB_LEVEL_1); | |
| 446 | + CNAudioReverbFilter reverbFilter = new CNAudioReverbFilter(); | |
| 447 | + reverbFilter.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_1); | |
| 448 | + filterBases.add(reverbFilter); | |
| 440 | 449 | break; |
| 441 | 450 | case 1: |
| 442 | - mStreamer.setAudioFilter(IStreamer.AUDIO_REVERB_LEVEL_2); | |
| 451 | + CNAudioReverbFilter reverbFilter1 = new CNAudioReverbFilter(); | |
| 452 | + reverbFilter1.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_2); | |
| 453 | + filterBases.add(reverbFilter1); | |
| 443 | 454 | break; |
| 444 | 455 | case 2: |
| 445 | - mStreamer.setAudioFilter(IStreamer.AUDIO_REVERB_LEVEL_3); | |
| 456 | + CNAudioReverbFilter reverbFilter2 = new CNAudioReverbFilter(); | |
| 457 | + reverbFilter2.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_3); | |
| 458 | + filterBases.add(reverbFilter2); | |
| 446 | 459 | break; |
| 447 | 460 | case 3: |
| 448 | - mStreamer.setAudioFilter(IStreamer.AUDIO_REVERB_LEVEL_4); | |
| 461 | + CNAudioReverbFilter reverbFilter3 = new CNAudioReverbFilter(); | |
| 462 | + reverbFilter3.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_4); | |
| 463 | + filterBases.add(reverbFilter3); | |
| 464 | + break; | |
| 465 | + default: | |
| 466 | + mStreamer.getAudioFilterMgt().setFilter(filterBases); | |
| 449 | 467 | break; |
| 450 | 468 | } |
| 451 | 469 | } | ... | ... |
app/src/main/java/com/cnlive/demo/stream/CNGlobalStreamer.java
0 → 100644
| 1 | +package com.cnlive.demo.stream; | |
| 2 | + | |
| 3 | +import android.annotation.SuppressLint; | |
| 4 | + | |
| 5 | +import com.cnlive.libs.stream.Streamer; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Created by Lynn on 2017/9/19. | |
| 9 | + */ | |
| 10 | + | |
| 11 | +public class CNGlobalStreamer { | |
| 12 | + | |
| 13 | + @SuppressLint("StaticFieldLeak") | |
| 14 | + private static Streamer sStreamer; | |
| 15 | + | |
| 16 | + private CNGlobalStreamer() { | |
| 17 | + | |
| 18 | + } | |
| 19 | + | |
| 20 | + public static void setInstance(Streamer streamer) { | |
| 21 | + sStreamer = streamer; | |
| 22 | + } | |
| 23 | + | |
| 24 | + public static Streamer getInstance() { | |
| 25 | + return sStreamer; | |
| 26 | + } | |
| 27 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/stream/CNStreamApplication.java
| ... | ... | @@ -3,6 +3,7 @@ package com.cnlive.demo.stream; |
| 3 | 3 | import android.app.Application; |
| 4 | 4 | |
| 5 | 5 | import com.cnlive.libs.util.Config; |
| 6 | +import com.facebook.drawee.backends.pipeline.Fresco; | |
| 6 | 7 | |
| 7 | 8 | /** |
| 8 | 9 | * Created by xiansong on 2016/7/22. |
| ... | ... | @@ -14,5 +15,8 @@ public class CNStreamApplication extends Application { |
| 14 | 15 | super.onCreate(); |
| 15 | 16 | |
| 16 | 17 | Config.init(this, "60_irn9gh9d49", "f7b6db55616d207fd5c07e2b580b5650c690aff58fb36d",true); |
| 18 | + | |
| 19 | + // 初始化fresco库,用来支持动态水印功能 | |
| 20 | + Fresco.initialize(this); | |
| 17 | 21 | } |
| 18 | 22 | } | ... | ... |
app/src/main/java/com/cnlive/demo/stream/FloatActivity.java
0 → 100644
| 1 | +package com.cnlive.demo.stream; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.app.Application; | |
| 5 | +import android.content.Intent; | |
| 6 | +import android.graphics.Color; | |
| 7 | +import android.graphics.PixelFormat; | |
| 8 | +import android.hardware.SensorManager; | |
| 9 | +import android.net.Uri; | |
| 10 | +import android.opengl.GLSurfaceView; | |
| 11 | +import android.os.Build; | |
| 12 | +import android.provider.Settings; | |
| 13 | +import android.support.annotation.Nullable; | |
| 14 | +import android.support.v7.app.AppCompatActivity; | |
| 15 | +import android.os.Bundle; | |
| 16 | +import android.util.Log; | |
| 17 | +import android.view.Gravity; | |
| 18 | +import android.view.KeyEvent; | |
| 19 | +import android.view.LayoutInflater; | |
| 20 | +import android.view.OrientationEventListener; | |
| 21 | +import android.view.Surface; | |
| 22 | +import android.view.View; | |
| 23 | +import android.view.Window; | |
| 24 | +import android.view.WindowManager; | |
| 25 | +import android.widget.ImageView; | |
| 26 | +import android.widget.RelativeLayout; | |
| 27 | +import android.widget.Toast; | |
| 28 | + | |
| 29 | +import com.cnlive.libs.stream.Streamer; | |
| 30 | + | |
| 31 | +public class FloatActivity extends Activity { | |
| 32 | + | |
| 33 | + private static final String TAG = "FloatViewActivity"; | |
| 34 | + | |
| 35 | + private final static int OVERLAY_PERMISSION_RESULT_CODE = 10; | |
| 36 | + | |
| 37 | + private GLSurfaceView mCameraView; | |
| 38 | + private int mLastRotation; | |
| 39 | + private OrientationEventListener mOrientationEventListener; | |
| 40 | + | |
| 41 | + //preview window just demo | |
| 42 | + private FloatView mFloatLayout; //悬浮窗口的layout, 可以是xml,也可以在代码中创建 | |
| 43 | + private ImageView mFloatClose; //关闭悬浮窗口 | |
| 44 | + private ImageView mFloatBack; //跳转到上一个页面,不停止推流 | |
| 45 | + private WindowManager.LayoutParams mWmParams; //layout的布局 | |
| 46 | + private WindowManager mWindowManager; | |
| 47 | + private volatile boolean mPreviewWindowShow = false; //悬浮摄像头预览window是否正在显示 | |
| 48 | + | |
| 49 | + //ButtonListener | |
| 50 | + private ButtonObserver mObserverButton; //所有按键响应 | |
| 51 | + | |
| 52 | + // logo path | |
| 53 | + private String mLogoPath = "file:///sdcard/test.png"; | |
| 54 | + | |
| 55 | + @Override | |
| 56 | + protected void onCreate(@Nullable Bundle savedInstanceState) { | |
| 57 | + Log.d(TAG, "onCreate"); | |
| 58 | + super.onCreate(savedInstanceState); | |
| 59 | + requestWindowFeature(Window.FEATURE_NO_TITLE); | |
| 60 | + setContentView(R.layout.activity_float); | |
| 61 | + mObserverButton = new ButtonObserver(); | |
| 62 | + mLastRotation = getDisplayRotation(); | |
| 63 | + mFloatBack = (ImageView) findViewById(R.id.float_back); | |
| 64 | + mFloatBack.setOnClickListener(mObserverButton); | |
| 65 | + | |
| 66 | + mOrientationEventListener = new OrientationEventListener(this, | |
| 67 | + SensorManager.SENSOR_DELAY_NORMAL) { | |
| 68 | + @Override | |
| 69 | + public void onOrientationChanged(int orientation) { | |
| 70 | + int rotation = getDisplayRotation(); | |
| 71 | + if (rotation != mLastRotation) { | |
| 72 | + Log.d(TAG, "Rotation changed " + mLastRotation + "->" + rotation); | |
| 73 | + onSwitchRotate(); | |
| 74 | + mLastRotation = rotation; | |
| 75 | + } | |
| 76 | + } | |
| 77 | + }; | |
| 78 | + if (mOrientationEventListener.canDetectOrientation()) { | |
| 79 | + mOrientationEventListener.enable(); | |
| 80 | + } | |
| 81 | + | |
| 82 | + addFloatViewWithPermCheck(); | |
| 83 | + } | |
| 84 | + | |
| 85 | + @Override | |
| 86 | + protected void onResume() { | |
| 87 | + Log.d(TAG, "onResume"); | |
| 88 | + super.onResume(); | |
| 89 | + } | |
| 90 | + | |
| 91 | + @Override | |
| 92 | + protected void onPause() { | |
| 93 | + Log.d(TAG, "onPause"); | |
| 94 | + super.onPause(); | |
| 95 | + } | |
| 96 | + | |
| 97 | + @Override | |
| 98 | + protected void onDestroy() { | |
| 99 | + Log.d(TAG, "onDestroy"); | |
| 100 | + super.onDestroy(); | |
| 101 | + if (mOrientationEventListener != null) { | |
| 102 | + mOrientationEventListener.disable(); | |
| 103 | + } | |
| 104 | + removeSurfaceWindow(); | |
| 105 | + CNGlobalStreamer.setInstance(null); | |
| 106 | + } | |
| 107 | + | |
| 108 | + @Override | |
| 109 | + public boolean onKeyDown(int keyCode, KeyEvent event) { | |
| 110 | + switch (keyCode) { | |
| 111 | + case KeyEvent.KEYCODE_BACK: | |
| 112 | + onBackoffClick(); | |
| 113 | + return true; | |
| 114 | + default: | |
| 115 | + break; | |
| 116 | + } | |
| 117 | + return super.onKeyDown(keyCode, event); | |
| 118 | + } | |
| 119 | + | |
| 120 | + private void onBackoffClick() { | |
| 121 | + CNGlobalStreamer.getInstance().pause(); | |
| 122 | + CNGlobalStreamer.getInstance().setDisplayPreview((GLSurfaceView) null); | |
| 123 | + FloatActivity.this.finish(); | |
| 124 | + } | |
| 125 | + | |
| 126 | + /** | |
| 127 | + * 初始化悬浮窗口示例 | |
| 128 | + */ | |
| 129 | + private void initSurfaceWindow() { | |
| 130 | + if (mWindowManager == null) { | |
| 131 | + mWmParams = new WindowManager.LayoutParams(); | |
| 132 | + mWindowManager = (WindowManager) getApplication(). | |
| 133 | + getSystemService(Application.WINDOW_SERVICE); | |
| 134 | + | |
| 135 | + //设置window type | |
| 136 | + mWmParams.type = WindowManager.LayoutParams.TYPE_TOAST; | |
| 137 | + | |
| 138 | + //设置图片格式,效果为背景透明 | |
| 139 | + mWmParams.format = PixelFormat.RGBA_8888; | |
| 140 | + | |
| 141 | + //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) | |
| 142 | + mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; | |
| 143 | + //开启硬件加速,以支持TextureView | |
| 144 | + mWmParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; | |
| 145 | + //接收touch事件 | |
| 146 | + mWmParams.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; | |
| 147 | + //排版不受限制 | |
| 148 | + mWmParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; | |
| 149 | + //调整悬浮窗显示的停靠位置为右侧底部 | |
| 150 | + mWmParams.gravity = Gravity.RIGHT | Gravity.BOTTOM; | |
| 151 | + | |
| 152 | + // 以屏幕左上角为原点,设置x、y初始值,相对于gravity | |
| 153 | + mWmParams.x = 0; | |
| 154 | + mWmParams.y = 0; | |
| 155 | + | |
| 156 | + //设置悬浮窗口长宽数据(这里取屏幕长宽的等比率缩小值作为悬浮窗口的长宽) | |
| 157 | + int screenWidth = mWindowManager.getDefaultDisplay().getWidth(); | |
| 158 | + int screenHeight = mWindowManager.getDefaultDisplay().getHeight(); | |
| 159 | + int width; | |
| 160 | + int height; | |
| 161 | + | |
| 162 | + boolean isLandscape = (getDisplayRotation() % 180) != 0; | |
| 163 | + if ((isLandscape && screenWidth < screenHeight) || | |
| 164 | + (!isLandscape) && screenWidth > screenHeight) { | |
| 165 | + screenWidth = mWindowManager.getDefaultDisplay().getHeight(); | |
| 166 | + screenHeight = mWindowManager.getDefaultDisplay().getWidth(); | |
| 167 | + } | |
| 168 | + | |
| 169 | + if (screenWidth < screenHeight) { | |
| 170 | + width = align(screenWidth / 3, 8); | |
| 171 | + height = align(screenHeight / 3, 8); | |
| 172 | + } else { | |
| 173 | + width = align(screenWidth / 3, 8); | |
| 174 | + height = align(screenHeight / 3, 8); | |
| 175 | + } | |
| 176 | + mWmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; | |
| 177 | + mWmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; | |
| 178 | + | |
| 179 | + LayoutInflater inflater = LayoutInflater.from(getApplication()); | |
| 180 | + | |
| 181 | + //获取浮动窗口视图所在布局 | |
| 182 | + mFloatLayout = (FloatView) inflater.inflate(R.layout.float_preview, null); | |
| 183 | + | |
| 184 | + mFloatClose = new ImageView(this); | |
| 185 | + mFloatClose.setImageResource(R.drawable.float_close); | |
| 186 | + mFloatClose.setOnClickListener(new View.OnClickListener() { | |
| 187 | + @Override | |
| 188 | + public void onClick(View v) { | |
| 189 | + onBackoffClick(); | |
| 190 | + } | |
| 191 | + }); | |
| 192 | + | |
| 193 | + mCameraView = new GLSurfaceView(this); | |
| 194 | + CNGlobalStreamer.getInstance().setDisplayPreview(mCameraView); | |
| 195 | + RelativeLayout.LayoutParams previewLayoutParams = | |
| 196 | + new RelativeLayout.LayoutParams(width, height); | |
| 197 | + mFloatLayout.addView(mCameraView, previewLayoutParams); | |
| 198 | + RelativeLayout.LayoutParams closeParams = | |
| 199 | + new RelativeLayout.LayoutParams(50, 50); | |
| 200 | + closeParams.setMargins(10,10,0,0); | |
| 201 | + mFloatLayout.addView(mFloatClose, closeParams); | |
| 202 | + | |
| 203 | + mFloatLayout.setWmParams(mWmParams); | |
| 204 | + } | |
| 205 | + } | |
| 206 | + | |
| 207 | + private class ButtonObserver implements View.OnClickListener { | |
| 208 | + @Override | |
| 209 | + public void onClick(View view) { | |
| 210 | + switch (view.getId()) { | |
| 211 | + case R.id.float_back: | |
| 212 | + onBackoffClick(); | |
| 213 | + break; | |
| 214 | + default: | |
| 215 | + break; | |
| 216 | + } | |
| 217 | + } | |
| 218 | + } | |
| 219 | + | |
| 220 | + private void addSurfaceWindow() { | |
| 221 | + if (mWindowManager != null) { | |
| 222 | + //添加mFloatLayout | |
| 223 | + mWindowManager.addView(mFloatLayout, mWmParams); | |
| 224 | + } | |
| 225 | + mPreviewWindowShow = true; | |
| 226 | + } | |
| 227 | + | |
| 228 | + private void removeSurfaceWindow() { | |
| 229 | + if (mWindowManager != null) { | |
| 230 | + mWindowManager.removeViewImmediate(mFloatLayout); | |
| 231 | + } | |
| 232 | + mPreviewWindowShow = false; | |
| 233 | + } | |
| 234 | + | |
| 235 | + private void updateViewPosition() { | |
| 236 | + if (mWmParams != null && mWindowManager != null) { | |
| 237 | + mWmParams.gravity = Gravity.RIGHT | Gravity.BOTTOM; | |
| 238 | + | |
| 239 | + // 以屏幕左上角为原点,设置x、y初始值,相对于gravity | |
| 240 | + mWmParams.x = 0; | |
| 241 | + mWmParams.y = 0; | |
| 242 | + mWindowManager.updateViewLayout(mFloatLayout, mWmParams); //刷新显示 | |
| 243 | + } | |
| 244 | + } | |
| 245 | + | |
| 246 | + private int align(int val, int align) { | |
| 247 | + return (val + align - 1) / align * align; | |
| 248 | + } | |
| 249 | + | |
| 250 | + private int getDisplayRotation() { | |
| 251 | + int rotation = getWindowManager().getDefaultDisplay().getRotation(); | |
| 252 | + switch (rotation) { | |
| 253 | + case Surface.ROTATION_0: | |
| 254 | + return 0; | |
| 255 | + case Surface.ROTATION_90: | |
| 256 | + return 90; | |
| 257 | + case Surface.ROTATION_180: | |
| 258 | + return 180; | |
| 259 | + case Surface.ROTATION_270: | |
| 260 | + return 270; | |
| 261 | + } | |
| 262 | + return 0; | |
| 263 | + } | |
| 264 | + | |
| 265 | + private void addFloatViewWithPermCheck() { | |
| 266 | + // 6.0 需要检查overlay权限 | |
| 267 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { | |
| 268 | + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, | |
| 269 | + Uri.parse("package:" + getPackageName())); | |
| 270 | + startActivityForResult(intent, OVERLAY_PERMISSION_RESULT_CODE); | |
| 271 | + } else { | |
| 272 | + initSurfaceWindow(); | |
| 273 | + addSurfaceWindow(); | |
| 274 | + } | |
| 275 | + } | |
| 276 | + | |
| 277 | + /** | |
| 278 | + * 申请overlay权限窗口返回 | |
| 279 | + * | |
| 280 | + * @param requestCode | |
| 281 | + * @param resultCode | |
| 282 | + * @param data | |
| 283 | + */ | |
| 284 | + @Override | |
| 285 | + protected void onActivityResult(int requestCode, int resultCode, Intent data) { | |
| 286 | + if (requestCode == OVERLAY_PERMISSION_RESULT_CODE) { | |
| 287 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
| 288 | + if (!Settings.canDrawOverlays(this)) { | |
| 289 | + // SYSTEM_ALERT_WINDOW permission not granted... | |
| 290 | + Toast.makeText(FloatActivity.this, "SYSTEM_ALERT_WINDOW not granted", | |
| 291 | + Toast.LENGTH_SHORT).show(); | |
| 292 | + } else { | |
| 293 | + initSurfaceWindow(); | |
| 294 | + addSurfaceWindow(); | |
| 295 | + } | |
| 296 | + } | |
| 297 | + } | |
| 298 | + } | |
| 299 | + | |
| 300 | + /** | |
| 301 | + * 旋转摄像头预览示例代码 | |
| 302 | + */ | |
| 303 | + private void onSwitchRotate() { | |
| 304 | + if (mPreviewWindowShow) { | |
| 305 | + int rotation = getDisplayRotation(); | |
| 306 | + Log.i(TAG, "onSwitchRotate: " + mLastRotation + "->" + rotation); | |
| 307 | + CNGlobalStreamer.getInstance().setRotateDegrees(rotation); | |
| 308 | + | |
| 309 | + boolean isLastLandscape = (mLastRotation % 180) != 0; | |
| 310 | + boolean isLandscape = (rotation % 180) != 0; | |
| 311 | + if (isLastLandscape != isLandscape) { | |
| 312 | + int width = mCameraView.getHeight(); | |
| 313 | + int height = mCameraView.getWidth(); | |
| 314 | + RelativeLayout.LayoutParams previewLayoutParams = | |
| 315 | + (RelativeLayout.LayoutParams) mCameraView.getLayoutParams(); | |
| 316 | + previewLayoutParams.width = width; | |
| 317 | + previewLayoutParams.height = height; | |
| 318 | + //更新CameraPreview布局 | |
| 319 | + mFloatLayout.updateViewLayout(mCameraView, previewLayoutParams); | |
| 320 | + | |
| 321 | + updateViewPosition(); | |
| 322 | + updateWaterMark(isLandscape); | |
| 323 | + } | |
| 324 | + } | |
| 325 | + } | |
| 326 | + | |
| 327 | + // update water mark position while screen orientation changed | |
| 328 | + private void updateWaterMark(boolean isLandscape) { | |
| 329 | + Streamer streamer = CNGlobalStreamer.getInstance(); | |
| 330 | + streamer.hideWaterMarkLogo(); | |
| 331 | + streamer.hideWaterMarkTime(); | |
| 332 | + if (!isLandscape) { | |
| 333 | + streamer.showWaterMarkLogo(mLogoPath, 0.08f, 0.04f, 0.20f, 0, 0.8f); | |
| 334 | + streamer.showWaterMarkTime(0.03f, 0.01f, 0.35f, Color.WHITE, 1.0f); | |
| 335 | + } else { | |
| 336 | + streamer.showWaterMarkLogo(mLogoPath, 0.05f, 0.09f, 0, 0.20f, 0.8f); | |
| 337 | + streamer.showWaterMarkTime(0.01f, 0.03f, 0.22f, Color.WHITE, 1.0f); | |
| 338 | + } | |
| 339 | + } | |
| 340 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/stream/FloatView.java
0 → 100644
| 1 | +package com.cnlive.demo.stream; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.util.AttributeSet; | |
| 5 | +import android.view.Gravity; | |
| 6 | +import android.view.MotionEvent; | |
| 7 | +import android.view.WindowManager; | |
| 8 | +import android.widget.RelativeLayout; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * Created by Lynn on 2017/9/19. | |
| 12 | + */ | |
| 13 | + | |
| 14 | +public class FloatView extends RelativeLayout { | |
| 15 | + private float mTouchStartX; | |
| 16 | + private float mTouchStartY; | |
| 17 | + private float mLastX; | |
| 18 | + private float mLastY; | |
| 19 | + private float mMaxX = 0; | |
| 20 | + private float mMaxY = 0; | |
| 21 | + | |
| 22 | + private WindowManager mWindowManager; | |
| 23 | + private WindowManager.LayoutParams mWmParams; | |
| 24 | + | |
| 25 | + public FloatView(Context context) { | |
| 26 | + super(context); | |
| 27 | + init(); | |
| 28 | + } | |
| 29 | + | |
| 30 | + public FloatView(Context context, AttributeSet attrs) { | |
| 31 | + super(context, attrs); | |
| 32 | + init(); | |
| 33 | + } | |
| 34 | + | |
| 35 | + public FloatView(Context context, AttributeSet attrs, int defStyleAttr) { | |
| 36 | + super(context, attrs, defStyleAttr); | |
| 37 | + init(); | |
| 38 | + } | |
| 39 | + | |
| 40 | + public void setWmParams(WindowManager.LayoutParams params) { | |
| 41 | + mWmParams = params; | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public boolean onTouchEvent(MotionEvent event) { | |
| 46 | + //获取相对屏幕的坐标,即以屏幕左上角为原点 | |
| 47 | + mLastX = event.getRawX(); | |
| 48 | + mLastY = event.getRawY(); | |
| 49 | + | |
| 50 | + switch (event.getAction()) { | |
| 51 | + case MotionEvent.ACTION_DOWN: | |
| 52 | + //获取相对View的坐标,即以此View左上角为原点 | |
| 53 | + mTouchStartX = event.getX(); | |
| 54 | + mTouchStartY = event.getY(); | |
| 55 | + break; | |
| 56 | + case MotionEvent.ACTION_MOVE: | |
| 57 | + updateViewPosition(); | |
| 58 | + break; | |
| 59 | + case MotionEvent.ACTION_UP: | |
| 60 | + mTouchStartX = 0; | |
| 61 | + mTouchStartY = 0; | |
| 62 | + break; | |
| 63 | + } | |
| 64 | + return true; | |
| 65 | + } | |
| 66 | + | |
| 67 | + private void init() { | |
| 68 | + mWindowManager = (WindowManager) getContext().getApplicationContext() | |
| 69 | + .getSystemService(Context.WINDOW_SERVICE); | |
| 70 | + } | |
| 71 | + | |
| 72 | + private void updateViewPosition() { | |
| 73 | + if (mWmParams != null) { | |
| 74 | + mMaxX = mWindowManager.getDefaultDisplay().getWidth() - this.getWidth(); | |
| 75 | + mMaxY = mWindowManager.getDefaultDisplay().getHeight() - this.getHeight(); | |
| 76 | + | |
| 77 | + //更新浮动窗口位置参数 | |
| 78 | + float newX = (mLastX - mTouchStartX); | |
| 79 | + float newY = (mLastY - mTouchStartY); | |
| 80 | + //以屏幕左上角为原点 | |
| 81 | + mWmParams.gravity = Gravity.LEFT | Gravity.TOP; | |
| 82 | + | |
| 83 | + //不能移除屏幕最左边和最上边 | |
| 84 | + if (newX < 0) { | |
| 85 | + newX = 0; | |
| 86 | + } | |
| 87 | + | |
| 88 | + if (newY < 0) { | |
| 89 | + newY = 0; | |
| 90 | + } | |
| 91 | + | |
| 92 | + //不能移出屏幕最右边和最下边 | |
| 93 | + if (newX > mMaxX) { | |
| 94 | + newX = mMaxX; | |
| 95 | + } | |
| 96 | + | |
| 97 | + if (newY > mMaxY) { | |
| 98 | + newY = mMaxY; | |
| 99 | + } | |
| 100 | + mWmParams.x = (int) newX; | |
| 101 | + mWmParams.y = (int) newY; | |
| 102 | + mWindowManager.updateViewLayout(this, mWmParams); //刷新显示 | |
| 103 | + | |
| 104 | + } | |
| 105 | + } | |
| 106 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/stream/StreamActivity.java
| ... | ... | @@ -8,7 +8,9 @@ import android.content.DialogInterface; |
| 8 | 8 | import android.content.Intent; |
| 9 | 9 | import android.content.pm.ActivityInfo; |
| 10 | 10 | import android.content.pm.PackageManager; |
| 11 | +import android.graphics.Bitmap; | |
| 11 | 12 | import android.graphics.Color; |
| 13 | +import android.hardware.Camera; | |
| 12 | 14 | import android.opengl.GLSurfaceView; |
| 13 | 15 | import android.os.Build; |
| 14 | 16 | import android.os.Bundle; |
| ... | ... | @@ -23,35 +25,42 @@ import android.view.Window; |
| 23 | 25 | import android.view.WindowManager; |
| 24 | 26 | import android.widget.AdapterView; |
| 25 | 27 | import android.widget.ArrayAdapter; |
| 28 | +import android.widget.Button; | |
| 26 | 29 | import android.widget.CheckBox; |
| 27 | 30 | import android.widget.Chronometer; |
| 28 | 31 | import android.widget.CompoundButton; |
| 32 | +import android.widget.ImageView; | |
| 29 | 33 | import android.widget.LinearLayout; |
| 30 | 34 | import android.widget.SeekBar; |
| 31 | 35 | import android.widget.TextView; |
| 32 | 36 | import android.widget.Toast; |
| 33 | 37 | |
| 34 | 38 | import com.cnlive.libs.stream.Streamer; |
| 39 | +import com.cnlive.libs.stream.base.ICNAudioEffectFilter; | |
| 35 | 40 | import com.cnlive.libs.stream.base.IStreamer; |
| 36 | 41 | import com.cnlive.libs.stream.base.ImgBeautyProFilter; |
| 37 | 42 | import com.cnlive.libs.stream.base.ImgFilterBase; |
| 43 | +import com.cnlive.libs.stream.filter.CNAudioEffectFilter; | |
| 44 | +import com.cnlive.libs.stream.filter.CNAudioFilterBase; | |
| 45 | +import com.cnlive.libs.stream.filter.CNAudioReverbFilter; | |
| 38 | 46 | import com.cnlive.libs.stream.model.ActivityConfig; |
| 39 | -import com.ksyun.media.player.IMediaPlayer; | |
| 40 | 47 | |
| 48 | +import java.io.BufferedOutputStream; | |
| 49 | +import java.io.FileNotFoundException; | |
| 50 | +import java.io.FileOutputStream; | |
| 51 | +import java.io.IOException; | |
| 52 | +import java.text.SimpleDateFormat; | |
| 53 | +import java.util.Date; | |
| 54 | +import java.util.LinkedList; | |
| 41 | 55 | import java.util.List; |
| 42 | 56 | |
| 43 | 57 | import static com.cnlive.libs.stream.base.IStreamer.ENCODE_METHOD_HARDWARE; |
| 44 | 58 | import static com.cnlive.libs.stream.base.IStreamer.ENCODE_METHOD_SOFTWARE_COMPAT; |
| 59 | +import static com.cnlive.libs.stream.base.IStreamer.ENCODE_PROFILE_BALANCE; | |
| 60 | +import static com.cnlive.libs.stream.base.IStreamer.ENCODE_PROFILE_HIGH_PERFORMANCE; | |
| 61 | +import static com.cnlive.libs.stream.base.IStreamer.ENCODE_PROFILE_LOW_POWER; | |
| 45 | 62 | import static com.cnlive.libs.stream.base.IStreamer.VIDEO_RESOLUTION_360P; |
| 46 | -import static com.cnlive.libs.stream.model.Filters.BEAUTY_DISABLE; | |
| 47 | -import static com.cnlive.libs.stream.model.Filters.BEAUTY_ILLUSION; | |
| 48 | -import static com.cnlive.libs.stream.model.Filters.BEAUTY_PRO; | |
| 49 | -import static com.cnlive.libs.stream.model.Filters.BEAUTY_SMOOTH; | |
| 50 | -import static com.cnlive.libs.stream.model.Filters.BEAUTY_SOFT; | |
| 51 | -import static com.cnlive.libs.stream.model.Filters.BEAUTY_SOFT_EXT; | |
| 52 | -import static com.cnlive.libs.stream.model.Filters.BEAUTY_SOFT_SHARPEN; | |
| 53 | -import static com.cnlive.libs.stream.model.Filters.DENOISE; | |
| 54 | -import static com.cnlive.libs.stream.model.Filters.SKIN_WHITEN; | |
| 63 | +import static com.cnlive.libs.stream.model.Filters.*; | |
| 55 | 64 | |
| 56 | 65 | /** |
| 57 | 66 | * Created by Administrator on 2016/8/29. |
| ... | ... | @@ -66,6 +75,8 @@ public class StreamActivity extends Activity { |
| 66 | 75 | private View mDeleteView; |
| 67 | 76 | private View mSwitchCameraView; |
| 68 | 77 | private View mFlashView; |
| 78 | + private ImageView mExposureView; | |
| 79 | + private ImageView mAddView; | |
| 69 | 80 | private TextView mShootingText; |
| 70 | 81 | private CheckBox mWaterMarkCheckBox; |
| 71 | 82 | private CheckBox mBeautyCheckBox; |
| ... | ... | @@ -74,6 +85,12 @@ public class StreamActivity extends Activity { |
| 74 | 85 | private CheckBox mMuteCheckBox; |
| 75 | 86 | private CheckBox mAudioOnlyCheckBox; |
| 76 | 87 | private CheckBox mFrontMirrorCheckBox; |
| 88 | + private CheckBox mBgImg; | |
| 89 | + private Button mAudioMix; | |
| 90 | + private Button mBgmFilter; | |
| 91 | + private Button mCaptureScreen; | |
| 92 | + | |
| 93 | + private VerticalSeekBar mExposureSeekBar; | |
| 77 | 94 | |
| 78 | 95 | private ButtonObserver mObserverButton; |
| 79 | 96 | private CheckBoxObserver mCheckBoxObserver; |
| ... | ... | @@ -86,12 +103,16 @@ public class StreamActivity extends Activity { |
| 86 | 103 | private boolean isFlashOpened = false; |
| 87 | 104 | private String mBgmPath = "/sdcard/test.mp3"; |
| 88 | 105 | private String mLogoPath = "file:///sdcard/cnlive_logo.png"; |
| 106 | + private String mAnimatedLogoPath = "assets://cnlive_logo.gif"; | |
| 107 | + private String mBgImagePath = "assets://bg.jpg"; | |
| 89 | 108 | |
| 90 | 109 | private String activityId; |
| 91 | 110 | private String channelId; |
| 92 | 111 | |
| 93 | 112 | private int isHorizontalScreen = 0; |
| 94 | 113 | |
| 114 | + private boolean mShowAnimatedLogo; | |
| 115 | + | |
| 95 | 116 | private final static int PERMISSION_REQUEST_CAMERA_AUDIOREC = 1; |
| 96 | 117 | private static final String START_STRING = "开始直播"; |
| 97 | 118 | private static final String STOP_STRING = "停止直播"; |
| ... | ... | @@ -112,6 +133,8 @@ public class StreamActivity extends Activity { |
| 112 | 133 | private TextView mRuddyText; |
| 113 | 134 | private AppCompatSeekBar mRuddySeekBar; |
| 114 | 135 | |
| 136 | + private AnimatedImageCapture animatedImageCapture; | |
| 137 | + | |
| 115 | 138 | public static void startActivity(Context context, |
| 116 | 139 | String activityID, |
| 117 | 140 | String channelID, |
| ... | ... | @@ -142,6 +165,7 @@ public class StreamActivity extends Activity { |
| 142 | 165 | channelId = bundle.getString(CHANNELID); |
| 143 | 166 | mIsLandscape = bundle.getBoolean(LANDSCAPE, false); |
| 144 | 167 | initStreamer(); |
| 168 | + startCameraPreviewWithPermCheck(); | |
| 145 | 169 | |
| 146 | 170 | } |
| 147 | 171 | |
| ... | ... | @@ -163,8 +187,15 @@ public class StreamActivity extends Activity { |
| 163 | 187 | //闪光灯 |
| 164 | 188 | mFlashView = findViewById(R.id.flash); |
| 165 | 189 | mFlashView.setOnClickListener(mObserverButton); |
| 190 | + //亮度 | |
| 191 | + mExposureView = (ImageView) findViewById(R.id.exposure); | |
| 192 | + mExposureView.setOnClickListener(mObserverButton); | |
| 193 | + //悬浮窗 | |
| 194 | + mAddView = (ImageView) findViewById(R.id.add); | |
| 195 | + mAddView.setOnClickListener(mObserverButton); | |
| 196 | + | |
| 166 | 197 | mCheckBoxObserver = new CheckBoxObserver(); |
| 167 | - //美艳 | |
| 198 | + //美颜 | |
| 168 | 199 | mBeautyCheckBox = (CheckBox) findViewById(R.id.click_to_switch_beauty); |
| 169 | 200 | mBeautyCheckBox.setOnCheckedChangeListener(mCheckBoxObserver); |
| 170 | 201 | //背景音乐 |
| ... | ... | @@ -186,6 +217,28 @@ public class StreamActivity extends Activity { |
| 186 | 217 | mAudioOnlyCheckBox = (CheckBox) findViewById(R.id.audio_only); |
| 187 | 218 | mAudioOnlyCheckBox.setOnCheckedChangeListener(mCheckBoxObserver); |
| 188 | 219 | |
| 220 | + //音频滤镜 | |
| 221 | + mAudioMix = (Button) findViewById(R.id.audio_mix); | |
| 222 | + mAudioMix.setOnClickListener(mObserverButton); | |
| 223 | + | |
| 224 | + //bgm升降调 | |
| 225 | + mBgmFilter = (Button) findViewById(R.id.bgm_filter); | |
| 226 | + mBgmFilter.setOnClickListener(mObserverButton); | |
| 227 | + | |
| 228 | + //截屏 | |
| 229 | + mCaptureScreen = (Button) findViewById(R.id.click_to_capture_screenshot); | |
| 230 | + mCaptureScreen.setOnClickListener(mObserverButton); | |
| 231 | + | |
| 232 | + //背景图推流 | |
| 233 | + mBgImg = (CheckBox) findViewById(R.id.bg_img); | |
| 234 | + mBgImg.setOnCheckedChangeListener(mCheckBoxObserver); | |
| 235 | + | |
| 236 | + //亮度调节seekbar | |
| 237 | + mExposureSeekBar = (VerticalSeekBar) findViewById(R.id.exposure_seekBar); | |
| 238 | + mExposureSeekBar.setProgress(50); | |
| 239 | + mExposureSeekBar.setSecondaryProgress(50); | |
| 240 | + mExposureSeekBar.setOnSeekBarChangeListener(verticalSeekBarlistener); | |
| 241 | + | |
| 189 | 242 | mBeautyChooseView = findViewById(R.id.beauty_choose); |
| 190 | 243 | mBeautySpinner = (AppCompatSpinner) findViewById(R.id.beauty_spin); |
| 191 | 244 | mBeautyGrindLayout = (LinearLayout) findViewById(R.id.beauty_grind); |
| ... | ... | @@ -213,8 +266,27 @@ public class StreamActivity extends Activity { |
| 213 | 266 | //分别设置预览和推流分辨率 |
| 214 | 267 | mStreamer.setPreviewResolution(VIDEO_RESOLUTION_360P); |
| 215 | 268 | mStreamer.setTargetResolution(VIDEO_RESOLUTION_360P); |
| 269 | +// //采集分辨率 | |
| 270 | +// mStreamer.setCameraCaptureResolution(VIDEO_RESOLUTION_360P); | |
| 216 | 271 | //设置编码模式(硬编/软编/软编兼容) |
| 217 | - mStreamer.setEncodeMethod(ENCODE_METHOD_SOFTWARE_COMPAT); | |
| 272 | + mStreamer.setEncodeMethod(ENCODE_METHOD_HARDWARE); | |
| 273 | + | |
| 274 | +// //设置场景 | |
| 275 | +// mStreamer.setVideoEncodeScene(IStreamer.ENCODE_SCENE_GAME); | |
| 276 | + | |
| 277 | +// //H265 | |
| 278 | +// mStreamer.setVideoCodecId(IStreamer.CODEC_ID_HEVC); | |
| 279 | +// //H264 | |
| 280 | +// mStreamer.setVideoCodecId(IStreamer.CODEC_ID_AVC); | |
| 281 | + | |
| 282 | +// //功耗 | |
| 283 | +// mStreamer.setVideoEncodeProfile(ENCODE_PROFILE_LOW_POWER);低 | |
| 284 | +// mStreamer.setVideoEncodeProfile(ENCODE_PROFILE_BALANCE);平衡 | |
| 285 | +// mStreamer.setVideoEncodeProfile(ENCODE_PROFILE_HIGH_PERFORMANCE);高性能 | |
| 286 | + | |
| 287 | +// //双声道推流 | |
| 288 | +// mStreamer.setAudioChannels(2); | |
| 289 | + | |
| 218 | 290 | |
| 219 | 291 | //设置横竖屏方向 |
| 220 | 292 | mStreamer.setRotateDegrees(mIsLandscape ? 90 : 0);//设置屏幕旋转角度 |
| ... | ... | @@ -243,22 +315,35 @@ public class StreamActivity extends Activity { |
| 243 | 315 | //设置错误回调,收到该回调后,一般是发生了严重错误,比如网络断开等 |
| 244 | 316 | mStreamer.setOnErrorListener(mOnErrorListener); |
| 245 | 317 | //设置美颜滤镜 |
| 246 | - mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), DENOISE.getFilter()); | |
| 318 | + mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), CN_FILTER_BEAUTY_DENOISE.getFilter()); | |
| 247 | 319 | mStreamer.setEnableImgBufBeauty(true); |
| 248 | 320 | |
| 249 | 321 | initBeautyUI(); |
| 250 | 322 | |
| 323 | + mStreamer.setFilterOnErrorListener(new ImgFilterBase.OnErrorListener() { | |
| 324 | + @Override | |
| 325 | + public void onError(int i) { | |
| 326 | + Toast.makeText(StreamActivity.this, "当前机型不支持该滤镜", Toast.LENGTH_SHORT).show(); | |
| 327 | + mStreamer.setFilter(CN_FILTER_BEAUTY_DISABLE.getFilter()); | |
| 328 | + } | |
| 329 | + }); | |
| 330 | + | |
| 251 | 331 | //手动变焦 |
| 252 | 332 | mStreamer.setCameraCapture(mStreamer.getCameraCapture()); |
| 253 | 333 | mCameraPreviewView.setOnTouchListener(mStreamer.getCameraTouchHelper()); |
| 254 | 334 | mStreamer.setCameraHintView(mCameraHintView); |
| 255 | 335 | mStreamer.setEnableTouchFocus(true); |
| 256 | 336 | mStreamer.setEnableZoom(true); |
| 337 | + | |
| 338 | + animatedImageCapture = new AnimatedImageCapture(this, mStreamer); | |
| 257 | 339 | } |
| 258 | 340 | |
| 259 | 341 | private void initBeautyUI() { |
| 260 | 342 | String[] items = new String[]{"DISABLE", "BEAUTY_SOFT", "SKIN_WHITEN", "BEAUTY_ILLUSION", |
| 261 | - "BEAUTY_DENOISE", "BEAUTY_SMOOTH", "BEAUTY_SOFT_EXT", "BEAUTY_SOFT_SHARPEN", "BEAUTY_PRO"}; | |
| 343 | + "BEAUTY_DENOISE", "BEAUTY_SMOOTH", "BEAUTY_PRO", "BEAUTY_PRO1", "BEAUTY_PRO2", "BEAUTY_PRO3", | |
| 344 | + "BEAUTY_PRO4", "ToneCurve", "复古", "胶片", "Amatorka", | |
| 345 | + "ELEGANCE", "1977", "Amaro", "Brannan", "EarlyBird", "Hefe", "Hudson", "ink", | |
| 346 | + "Lomo", "LordKelvin", "Nash", "Rise", "Sierra", "Sutro", "Toaster", "Valencia", "Walden", "XproII"}; | |
| 262 | 347 | ArrayAdapter<String> adapter = new ArrayAdapter<>(this, |
| 263 | 348 | android.R.layout.simple_spinner_item, items); |
| 264 | 349 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
| ... | ... | @@ -270,34 +355,35 @@ public class StreamActivity extends Activity { |
| 270 | 355 | if (textView != null) { |
| 271 | 356 | textView.setTextColor(getResources().getColor(R.color.colorAccent)); |
| 272 | 357 | } |
| 273 | - switch (position) { | |
| 274 | - case 0: | |
| 275 | - mStreamer.setFilter(BEAUTY_DISABLE.getFilter()); | |
| 276 | - break; | |
| 277 | - case 1: | |
| 278 | - mStreamer.setFilter(BEAUTY_SOFT.getFilter()); | |
| 279 | - break; | |
| 280 | - case 2: | |
| 281 | - mStreamer.setFilter(SKIN_WHITEN.getFilter()); | |
| 282 | - break; | |
| 283 | - case 3: | |
| 284 | - mStreamer.setFilter(BEAUTY_ILLUSION.getFilter()); | |
| 285 | - break; | |
| 286 | - case 4: | |
| 287 | - mStreamer.setFilter(DENOISE.getFilter()); | |
| 288 | - break; | |
| 289 | - case 5: | |
| 290 | - mStreamer.setFilter(BEAUTY_SMOOTH.getFilter()); | |
| 291 | - break; | |
| 292 | - case 6: | |
| 293 | - mStreamer.setFilter(BEAUTY_SOFT_EXT.getFilter()); | |
| 294 | - break; | |
| 295 | - case 7: | |
| 296 | - mStreamer.setFilter(BEAUTY_SOFT_SHARPEN.getFilter()); | |
| 297 | - break; | |
| 298 | - case 8: | |
| 299 | - mStreamer.setFilter(BEAUTY_PRO.getFilter()); | |
| 300 | - break; | |
| 358 | + | |
| 359 | + if (position == 0) { | |
| 360 | + mStreamer.setFilter(CN_FILTER_BEAUTY_DISABLE.getFilter()); | |
| 361 | + } else if (position <= 5) { | |
| 362 | + mStreamer.setFilter(position + 15); | |
| 363 | + } else if (position == 6) { | |
| 364 | + mStreamer.setFilter(CN_FILTER_BEAUTY_PRO.getFilter()); | |
| 365 | + } else if (position == 7) { | |
| 366 | + mStreamer.setFilter(CN_FILTER_BEAUTY_PRO1.getFilter()); | |
| 367 | + } else if (position == 8) { | |
| 368 | + mStreamer.setFilter(CN_FILTER_BEAUTY_PRO2.getFilter()); | |
| 369 | + } else if (position == 9) { | |
| 370 | + mStreamer.setFilter(CN_FILTER_BEAUTY_PRO3.getFilter()); | |
| 371 | + } else if (position == 10) { | |
| 372 | + mStreamer.setFilter(CN_FILTER_BEAUTY_PRO4.getFilter()); | |
| 373 | + } else if (position == 11) { | |
| 374 | + mStreamer.setToneCurveFilterStream(StreamActivity.this.getResources().openRawResource(R.raw.tone_cuver_sample)); | |
| 375 | + } else if (position == 12) { | |
| 376 | + mStreamer.setToneCurveFilterStream(StreamActivity.this.getResources().openRawResource(R.raw.fugu)); | |
| 377 | + } else if (position == 13) { | |
| 378 | + mStreamer.setToneCurveFilterStream(StreamActivity.this.getResources().openRawResource(R.raw.jiaopian)); | |
| 379 | + } else if (position == 14) { | |
| 380 | + //设置风格滤镜 | |
| 381 | + mStreamer.setSpecialEffectsFilter(CN_SPECIAL_EFFECT_AMATORKA.getFilter()); | |
| 382 | + } else if (position == 15) { | |
| 383 | + mStreamer.setSpecialEffectsFilter(CN_SPECIAL_EFFECT_ELEGANCE.getFilter()); | |
| 384 | + } else if (position >= 16 && position <= 32) { | |
| 385 | + int diff = position - 16; | |
| 386 | + mStreamer.setFilter(CN_FILTER_BEAUTY_1977.getFilter() + diff); | |
| 301 | 387 | } |
| 302 | 388 | List<ImgFilterBase> filters = mStreamer.getFilter(); |
| 303 | 389 | if (filters != null && !filters.isEmpty()) { |
| ... | ... | @@ -363,10 +449,81 @@ public class StreamActivity extends Activity { |
| 363 | 449 | mBeautySpinner.setSelection(4); |
| 364 | 450 | } |
| 365 | 451 | |
| 452 | + private boolean[] mChooseFilter = {false, false, false, false | |
| 453 | + , false, false, false, false, false, false}; | |
| 454 | + | |
| 455 | + private void showChooseAudioFilter() { | |
| 456 | + AlertDialog alertDialog; | |
| 457 | + alertDialog = new AlertDialog.Builder(this) | |
| 458 | + .setTitle("音频滤镜") | |
| 459 | + .setMultiChoiceItems(new String[]{"录音棚", "KTV", "小舞台", "演唱会", "萝莉", | |
| 460 | + "大叔", "庄严", "机器人"}, mChooseFilter, | |
| 461 | + new DialogInterface.OnMultiChoiceClickListener() { | |
| 462 | + @Override | |
| 463 | + public void onClick(DialogInterface dialog, int which, boolean isChecked) { | |
| 464 | + if (isChecked) { | |
| 465 | + mChooseFilter[which] = true; | |
| 466 | + } | |
| 467 | + } | |
| 468 | + }).setPositiveButton("确定", new DialogInterface.OnClickListener() { | |
| 469 | + @Override | |
| 470 | + public void onClick(DialogInterface dialog, int which) { | |
| 471 | + List<CNAudioFilterBase> filterBases = new LinkedList<CNAudioFilterBase>(); | |
| 472 | + if (mChooseFilter[0]) { | |
| 473 | + CNAudioReverbFilter reverbFilter = new CNAudioReverbFilter(); | |
| 474 | + reverbFilter.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_1); | |
| 475 | + filterBases.add(reverbFilter); | |
| 476 | + } | |
| 477 | + if (mChooseFilter[1]) { | |
| 478 | + CNAudioReverbFilter reverbFilter = new CNAudioReverbFilter(); | |
| 479 | + reverbFilter.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_2); | |
| 480 | + filterBases.add(reverbFilter); | |
| 481 | + } | |
| 482 | + if (mChooseFilter[2]) { | |
| 483 | + CNAudioReverbFilter reverbFilter = new CNAudioReverbFilter(); | |
| 484 | + reverbFilter.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_3); | |
| 485 | + filterBases.add(reverbFilter); | |
| 486 | + } | |
| 487 | + if (mChooseFilter[3]) { | |
| 488 | + CNAudioReverbFilter reverbFilter = new CNAudioReverbFilter(); | |
| 489 | + reverbFilter.setReverbLevel(IStreamer.AUDIO_REVERB_LEVEL_4); | |
| 490 | + filterBases.add(reverbFilter); | |
| 491 | + } | |
| 492 | + if (mChooseFilter[4]) { | |
| 493 | + CNAudioEffectFilter effectFilter = new CNAudioEffectFilter(ICNAudioEffectFilter.AUDIO_EFFECT_TYPE_FEMALE); | |
| 494 | + filterBases.add(effectFilter); | |
| 495 | + } | |
| 496 | + if (mChooseFilter[5]) { | |
| 497 | + CNAudioEffectFilter effectFilter = new CNAudioEffectFilter(ICNAudioEffectFilter.AUDIO_EFFECT_TYPE_MALE); | |
| 498 | + filterBases.add(effectFilter); | |
| 499 | + } | |
| 500 | + | |
| 501 | + if (mChooseFilter[6]) { | |
| 502 | + CNAudioEffectFilter effectFilter = new CNAudioEffectFilter(ICNAudioEffectFilter.AUDIO_EFFECT_TYPE_HEROIC); | |
| 503 | + filterBases.add(effectFilter); | |
| 504 | + } | |
| 505 | + | |
| 506 | + if (mChooseFilter[7]) { | |
| 507 | + CNAudioEffectFilter effectFilter = new CNAudioEffectFilter(ICNAudioEffectFilter.AUDIO_EFFECT_TYPE_ROBOT); | |
| 508 | + filterBases.add(effectFilter); | |
| 509 | + } | |
| 510 | + | |
| 511 | + if (!mChooseFilter[0] && !mChooseFilter[1] && !mChooseFilter[2] && !mChooseFilter[3] && !mChooseFilter[4] && !mChooseFilter[5] && !mChooseFilter[6] && !mChooseFilter[7]) { | |
| 512 | + filterBases = null; | |
| 513 | + } | |
| 514 | + mStreamer.getAudioFilterMgt().setFilter(filterBases); | |
| 515 | + | |
| 516 | + dialog.dismiss(); | |
| 517 | + } | |
| 518 | + }).create(); | |
| 519 | + alertDialog.setCancelable(false); | |
| 520 | + alertDialog.show(); | |
| 521 | + } | |
| 522 | + | |
| 366 | 523 | @Override |
| 367 | 524 | public void onResume() { |
| 368 | 525 | super.onResume(); |
| 369 | - startCameraPreviewWithPermCheck(); | |
| 526 | + mStreamer.setDisplayPreview(mCameraPreviewView); | |
| 370 | 527 | mStreamer.resume(); |
| 371 | 528 | if (mStreamer.isRecording() && !mAudioOnlyCheckBox.isChecked()) { |
| 372 | 529 | mStreamer.setAudioOnly(false); |
| ... | ... | @@ -380,17 +537,18 @@ public class StreamActivity extends Activity { |
| 380 | 537 | public void onPause() { |
| 381 | 538 | super.onPause(); |
| 382 | 539 | mStreamer.pause(); |
| 383 | - mStreamer.stopCameraPreview(); | |
| 384 | - if (mStreamer.isRecording() && !mAudioOnlyCheckBox.isChecked()) { | |
| 385 | - mStreamer.setAudioOnly(true); | |
| 386 | - mStreamer.setMuteAudio(true); | |
| 387 | - } | |
| 540 | +// mStreamer.stopCameraPreview(); | |
| 541 | +// if (mStreamer.isRecording() && !mAudioOnlyCheckBox.isChecked()) { | |
| 542 | +// mStreamer.setAudioOnly(true); | |
| 543 | +// mStreamer.setMuteAudio(true); | |
| 544 | +// } | |
| 388 | 545 | hideWaterMark(); |
| 389 | 546 | } |
| 390 | 547 | |
| 391 | 548 | @Override |
| 392 | 549 | public void onDestroy() { |
| 393 | 550 | super.onDestroy(); |
| 551 | + animatedImageCapture.stop(); | |
| 394 | 552 | if (mMainHandler != null) { |
| 395 | 553 | mMainHandler.removeCallbacksAndMessages(null); |
| 396 | 554 | mMainHandler = null; |
| ... | ... | @@ -429,6 +587,87 @@ public class StreamActivity extends Activity { |
| 429 | 587 | mRecording = false; |
| 430 | 588 | } |
| 431 | 589 | |
| 590 | + private int mChooseBGMFilter = 3; | |
| 591 | + private String[] mBGMFilterName = {"-3", "-2", "-1", "0", "1", "2", "3"}; | |
| 592 | + | |
| 593 | + //bgm升降调 | |
| 594 | + private void onChooseBGMFilter() { | |
| 595 | + AlertDialog alertDialog; | |
| 596 | + alertDialog = new AlertDialog.Builder(this) | |
| 597 | + .setTitle("请选择背景音乐变调等级") | |
| 598 | + .setSingleChoiceItems(mBGMFilterName, mChooseBGMFilter, new DialogInterface.OnClickListener() { | |
| 599 | + @Override | |
| 600 | + public void onClick(DialogInterface dialog, int which) { | |
| 601 | + mChooseBGMFilter = which; | |
| 602 | + } | |
| 603 | + }) | |
| 604 | + .setPositiveButton("确定", new DialogInterface.OnClickListener() { | |
| 605 | + @Override | |
| 606 | + public void onClick(DialogInterface dialog, int which) { | |
| 607 | + int level = Integer.parseInt(mBGMFilterName[mChooseBGMFilter]); | |
| 608 | + CNAudioEffectFilter audioEffectFilter = new CNAudioEffectFilter(ICNAudioEffectFilter.AUDIO_EFFECT_TYPE_PITCH); | |
| 609 | + audioEffectFilter.setPitchLevel(level); | |
| 610 | + mStreamer.getBGMAudioFilterMgt().setFilter(audioEffectFilter); | |
| 611 | + dialog.dismiss(); | |
| 612 | + } | |
| 613 | + }) | |
| 614 | + .create(); | |
| 615 | + alertDialog.setCancelable(false); | |
| 616 | + alertDialog.show(); | |
| 617 | + } | |
| 618 | + | |
| 619 | + //截屏 | |
| 620 | + private void onCaptureScreenShotClick() { | |
| 621 | + mStreamer.requestScreenShot(new IStreamer.ScreenShotListener() { | |
| 622 | + @Override | |
| 623 | + public void onBitmapAvailable(Bitmap bitmap) { | |
| 624 | + BufferedOutputStream bos = null; | |
| 625 | + try { | |
| 626 | + Date date = new Date(); | |
| 627 | + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); | |
| 628 | + final String fileName = "/sdcard/screenshot" + dateFormat.format(date) + ".jpg"; | |
| 629 | + | |
| 630 | + bos = new BufferedOutputStream(new FileOutputStream(fileName)); | |
| 631 | + | |
| 632 | + if (bitmap != null) { | |
| 633 | + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos); | |
| 634 | + runOnUiThread(new Runnable() { | |
| 635 | + @Override | |
| 636 | + public void run() { | |
| 637 | + Toast.makeText(StreamActivity.this, "保存截图到" + fileName, Toast.LENGTH_SHORT).show(); | |
| 638 | + } | |
| 639 | + }); | |
| 640 | + } | |
| 641 | + } catch (FileNotFoundException e) { | |
| 642 | + e.printStackTrace(); | |
| 643 | + } finally { | |
| 644 | + if (bos != null) | |
| 645 | + try { | |
| 646 | + bos.close(); | |
| 647 | + } catch (IOException e) { | |
| 648 | + e.printStackTrace(); | |
| 649 | + } | |
| 650 | + } | |
| 651 | + } | |
| 652 | + }); | |
| 653 | + } | |
| 654 | + | |
| 655 | + //亮度调节 | |
| 656 | + private void onExposureClick() { | |
| 657 | + if (mExposureSeekBar.getVisibility() == View.VISIBLE) { | |
| 658 | + mExposureSeekBar.setVisibility(View.GONE); | |
| 659 | + } else { | |
| 660 | + mExposureSeekBar.setVisibility(View.VISIBLE); | |
| 661 | + } | |
| 662 | + } | |
| 663 | + | |
| 664 | + private void onAddClick() { | |
| 665 | + Intent intent = new Intent(getApplicationContext(), FloatActivity.class); | |
| 666 | + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
| 667 | + CNGlobalStreamer.setInstance(mStreamer); | |
| 668 | + startActivity(intent); | |
| 669 | + } | |
| 670 | + | |
| 432 | 671 | private class ButtonObserver implements View.OnClickListener { |
| 433 | 672 | @Override |
| 434 | 673 | public void onClick(View view) { |
| ... | ... | @@ -445,12 +684,28 @@ public class StreamActivity extends Activity { |
| 445 | 684 | case R.id.click_to_shoot: |
| 446 | 685 | onShootClick(); |
| 447 | 686 | break; |
| 687 | + case R.id.audio_mix: | |
| 688 | + showChooseAudioFilter(); | |
| 689 | + break; | |
| 690 | + case R.id.bgm_filter: | |
| 691 | + onChooseBGMFilter(); | |
| 692 | + break; | |
| 693 | + case R.id.click_to_capture_screenshot: | |
| 694 | + onCaptureScreenShotClick(); | |
| 695 | + break; | |
| 696 | + case R.id.exposure: | |
| 697 | + onExposureClick(); | |
| 698 | + break; | |
| 699 | + case R.id.add: | |
| 700 | + onAddClick(); | |
| 701 | + break; | |
| 448 | 702 | default: |
| 449 | 703 | break; |
| 450 | 704 | } |
| 451 | 705 | } |
| 452 | 706 | } |
| 453 | 707 | |
| 708 | + | |
| 454 | 709 | private class CheckBoxObserver implements CompoundButton.OnCheckedChangeListener { |
| 455 | 710 | |
| 456 | 711 | @Override |
| ... | ... | @@ -477,6 +732,9 @@ public class StreamActivity extends Activity { |
| 477 | 732 | case R.id.audio_only: |
| 478 | 733 | onAudioOnlyChecked(isChecked); |
| 479 | 734 | break; |
| 735 | + case R.id.bg_img: | |
| 736 | + onBgImageChecked(isChecked); | |
| 737 | + break; | |
| 480 | 738 | default: |
| 481 | 739 | break; |
| 482 | 740 | } |
| ... | ... | @@ -484,6 +742,17 @@ public class StreamActivity extends Activity { |
| 484 | 742 | |
| 485 | 743 | } |
| 486 | 744 | |
| 745 | + //背景图推流 | |
| 746 | + private void onBgImageChecked(boolean isChecked) { | |
| 747 | + if (isChecked) { | |
| 748 | + mStreamer.stopCameraPreview(); | |
| 749 | + mStreamer.startImageCapture(mBgImagePath); | |
| 750 | + } else { | |
| 751 | + mStreamer.startCameraPreview(); | |
| 752 | + mStreamer.stopImageCapture(); | |
| 753 | + } | |
| 754 | + } | |
| 755 | + | |
| 487 | 756 | //转换摄像头 |
| 488 | 757 | private void onSwitchCamera() { |
| 489 | 758 | mStreamer.switchCamera(); |
| ... | ... | @@ -536,11 +805,11 @@ public class StreamActivity extends Activity { |
| 536 | 805 | private void onBeautyChecked(boolean isChecked) { |
| 537 | 806 | if (mStreamer.getVideoEncodeMethod() == |
| 538 | 807 | IStreamer.ENCODE_METHOD_SOFTWARE_COMPAT) { |
| 539 | - mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), | |
| 540 | - DENOISE.getFilter()); | |
| 541 | - mStreamer.setEnableImgBufBeauty(true); | |
| 808 | + mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), isChecked ? | |
| 809 | + CN_FILTER_BEAUTY_DENOISE.getFilter() : | |
| 810 | + CN_FILTER_BEAUTY_DISABLE.getFilter()); | |
| 811 | + mStreamer.setEnableImgBufBeauty(isChecked); | |
| 542 | 812 | } else { |
| 543 | -// showChooseFilter(); | |
| 544 | 813 | mBeautyChooseView.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); |
| 545 | 814 | } |
| 546 | 815 | } |
| ... | ... | @@ -587,17 +856,21 @@ public class StreamActivity extends Activity { |
| 587 | 856 | //显示水印 |
| 588 | 857 | private void showWaterMark() { |
| 589 | 858 | if (!mIsLandscape) { |
| 590 | - /** | |
| 591 | - * 设置并显示logo水印 | |
| 592 | - * | |
| 593 | - * @param path logo图片文件的路径 | |
| 594 | - * @param x logo的显示位置,0-1之间,相对于视频 | |
| 595 | - * @param y logo的显示位置,0-1之间,相对于视频 | |
| 596 | - * @param w logo的显示宽度,0-1之间,相对于视频,为0时会根据h及logo图片的比例自适应 | |
| 597 | - * @param h logo的显示高度,0-1之间,相对于视频,为0时会根据w及logo图片的比例自适应 | |
| 598 | - * @param alpha logo的透明度,0-1之间 | |
| 599 | - */ | |
| 600 | - mStreamer.showWaterMarkLogo(mLogoPath, 0.08f, 0.04f, 0.20f, 0, 0.8f); | |
| 859 | + if (!mShowAnimatedLogo) { | |
| 860 | + /** | |
| 861 | + * 设置并显示logo水印 | |
| 862 | + * | |
| 863 | + * @param path logo图片文件的路径 | |
| 864 | + * @param x logo的显示位置,0-1之间,相对于视频 | |
| 865 | + * @param y logo的显示位置,0-1之间,相对于视频 | |
| 866 | + * @param w logo的显示宽度,0-1之间,相对于视频,为0时会根据h及logo图片的比例自适应 | |
| 867 | + * @param h logo的显示高度,0-1之间,相对于视频,为0时会根据w及logo图片的比例自适应 | |
| 868 | + * @param alpha logo的透明度,0-1之间 | |
| 869 | + */ | |
| 870 | + mStreamer.showWaterMarkLogo(mLogoPath, 0.08f, 0.04f, 0.20f, 0, 0.8f); | |
| 871 | + } else { | |
| 872 | + animatedImageCapture.showAnimatedWaterMard(mAnimatedLogoPath, 0.08f, 0.04f, 0.25f, 0, 0.8f); | |
| 873 | + } | |
| 601 | 874 | |
| 602 | 875 | /** |
| 603 | 876 | * 在推流视频中显示时间水印 |
| ... | ... | @@ -610,15 +883,21 @@ public class StreamActivity extends Activity { |
| 610 | 883 | */ |
| 611 | 884 | mStreamer.showWaterMarkTime(0.03f, 0.01f, 0.35f, Color.RED, 1.0f); |
| 612 | 885 | } else { |
| 613 | - mStreamer.showWaterMarkLogo(mLogoPath, 0.05f, 0.09f, 0, 0.08f, 0.8f); | |
| 886 | + if (!mShowAnimatedLogo) { | |
| 887 | + mStreamer.showWaterMarkLogo(mLogoPath, 0.05f, 0.09f, 0, 0.08f, 0.8f); | |
| 888 | + } else { | |
| 889 | + animatedImageCapture.showAnimatedWaterMard(mAnimatedLogoPath, 0.05f, 0.09f, 0, 0.25f, 0.8f); | |
| 890 | + } | |
| 614 | 891 | mStreamer.showWaterMarkTime(0.01f, 0.03f, 0.22f, Color.RED, 1.0f); |
| 615 | 892 | } |
| 893 | + mShowAnimatedLogo = !mShowAnimatedLogo; | |
| 616 | 894 | } |
| 617 | 895 | |
| 618 | 896 | //隐藏水印 |
| 619 | 897 | private void hideWaterMark() { |
| 620 | 898 | mStreamer.hideWaterMarkLogo(); |
| 621 | 899 | mStreamer.hideWaterMarkTime(); |
| 900 | + animatedImageCapture.hideAnimatedWaterMark(); | |
| 622 | 901 | } |
| 623 | 902 | |
| 624 | 903 | //摄像头权限检测 |
| ... | ... | @@ -643,50 +922,6 @@ public class StreamActivity extends Activity { |
| 643 | 922 | } |
| 644 | 923 | } |
| 645 | 924 | |
| 646 | - //选择美颜滤镜 | |
| 647 | - private void showChooseFilter() { | |
| 648 | - AlertDialog alertDialog; | |
| 649 | - alertDialog = new AlertDialog.Builder(this) | |
| 650 | - .setTitle("请选择美颜滤镜") | |
| 651 | - .setSingleChoiceItems( | |
| 652 | - /** | |
| 653 | - * BEAUTY_DISABLE 禁用美颜 无 | |
| 654 | - * DENOISE 自然 美白(中)+磨皮(低)+红润(低) | |
| 655 | - * SKIN_WHITEN 白肤 美白(强)+磨皮(中)+红润(低) | |
| 656 | - * BEAUTY_ILLUSION 柔肤 美白(中)+磨皮(中)+红润(低) | |
| 657 | - * BEAUTY_SMOOTH 粉嫩 美白(中)+磨皮(强)+红润(中) | |
| 658 | - * BEAUTY_SOFT 嫩肤 美白(中)+磨皮(中)+红润(强) | |
| 659 | - */ | |
| 660 | - new String[]{"BEAUTY_SOFT", "SKIN_WHITEN", "BEAUTY_ILLUSION", "DENOISE", | |
| 661 | - "BEAUTY_SMOOTH"}, -1, | |
| 662 | - new DialogInterface.OnClickListener() { | |
| 663 | - @Override | |
| 664 | - public void onClick(DialogInterface dialog, int which) { | |
| 665 | - switch (which) { | |
| 666 | - case 0: | |
| 667 | - mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), BEAUTY_SOFT.getFilter()); | |
| 668 | - break; | |
| 669 | - case 1: | |
| 670 | - mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), SKIN_WHITEN.getFilter()); | |
| 671 | - break; | |
| 672 | - case 2: | |
| 673 | - mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), BEAUTY_ILLUSION.getFilter()); | |
| 674 | - break; | |
| 675 | - case 3: | |
| 676 | - mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), DENOISE.getFilter()); | |
| 677 | - break; | |
| 678 | - case 4: | |
| 679 | - mStreamer.getImgTexFilterMgt().setFilter(mStreamer.getGLRender(), BEAUTY_SMOOTH.getFilter()); | |
| 680 | - break; | |
| 681 | - } | |
| 682 | - dialog.dismiss(); | |
| 683 | - } | |
| 684 | - }) | |
| 685 | - .create(); | |
| 686 | - alertDialog.setCancelable(false); | |
| 687 | - alertDialog.show(); | |
| 688 | - } | |
| 689 | - | |
| 690 | 925 | private IStreamer.OnInfoListener mOnInfoListener = new IStreamer.OnInfoListener() { |
| 691 | 926 | @Override |
| 692 | 927 | public void onInfo(int what, int msg1, int msg2) { |
| ... | ... | @@ -894,4 +1129,33 @@ public class StreamActivity extends Activity { |
| 894 | 1129 | return ""; |
| 895 | 1130 | } |
| 896 | 1131 | }; |
| 1132 | + | |
| 1133 | + private VerticalSeekBar.OnSeekBarChangeListener verticalSeekBarlistener = new VerticalSeekBar.OnSeekBarChangeListener() { | |
| 1134 | + @Override | |
| 1135 | + public void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser) { | |
| 1136 | + Camera.Parameters parameters = mStreamer.getCameraCapture().getCameraParameters(); | |
| 1137 | + if (parameters != null) { | |
| 1138 | + int minValue = parameters.getMinExposureCompensation(); | |
| 1139 | + int maxValue = parameters.getMaxExposureCompensation(); | |
| 1140 | + int range = 100 / (maxValue - minValue); | |
| 1141 | + parameters.setExposureCompensation(progress / range - maxValue); | |
| 1142 | + } | |
| 1143 | + mStreamer.getCameraCapture().setCameraParameters(parameters); | |
| 1144 | + } | |
| 1145 | + | |
| 1146 | + @Override | |
| 1147 | + public void onStartTrackingTouch(VerticalSeekBar seekBar) { | |
| 1148 | + | |
| 1149 | + } | |
| 1150 | + | |
| 1151 | + @Override | |
| 1152 | + public void onStopTrackingTouch(VerticalSeekBar seekBar) { | |
| 1153 | + | |
| 1154 | + } | |
| 1155 | + | |
| 1156 | + @Override | |
| 1157 | + public void onRequestDisallowInterceptTouchEvent(boolean enable) { | |
| 1158 | + | |
| 1159 | + } | |
| 1160 | + }; | |
| 897 | 1161 | } | ... | ... |
app/src/main/java/com/cnlive/demo/stream/VerticalSeekBar.java
0 → 100644
| 1 | +package com.cnlive.demo.stream; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.graphics.drawable.Drawable; | |
| 5 | +import android.graphics.drawable.GradientDrawable; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.view.Gravity; | |
| 8 | +import android.view.MotionEvent; | |
| 9 | +import android.view.View; | |
| 10 | +import android.widget.FrameLayout; | |
| 11 | +import android.widget.TextView; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * 自定义竖直SeekBar. | |
| 15 | + */ | |
| 16 | + | |
| 17 | +public class VerticalSeekBar extends FrameLayout { | |
| 18 | + public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) { | |
| 19 | + super(context, attrs, defStyle); | |
| 20 | + initialize(context); | |
| 21 | + } | |
| 22 | + | |
| 23 | + public VerticalSeekBar(Context context, AttributeSet attrs) { | |
| 24 | + super(context, attrs); | |
| 25 | + initialize(context); | |
| 26 | + } | |
| 27 | + | |
| 28 | + public VerticalSeekBar(Context context) { | |
| 29 | + super(context); | |
| 30 | + initialize(context); | |
| 31 | + } | |
| 32 | + | |
| 33 | + private View mProgressBg; | |
| 34 | + private View mSecondaryProgressBg; | |
| 35 | + private View mBg; | |
| 36 | + private FrameLayout mSlider; | |
| 37 | + private int mProgress = 50; | |
| 38 | + private int mSecondaryProgress = 50; | |
| 39 | + private int mMax = 100; | |
| 40 | + private boolean mTrackingTouch = false; | |
| 41 | + private OnSeekBarChangeListener mOnSeekBarChangeListener; | |
| 42 | + private int mBGSize = 6; //进度条大小 | |
| 43 | + private int mBGRadius = 20; //进度条圆角 | |
| 44 | + private int mCircleSize = 50; //圆的大小 | |
| 45 | + private TextView mTextView; //字 | |
| 46 | + | |
| 47 | + | |
| 48 | + public interface OnSeekBarChangeListener { | |
| 49 | + void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser); | |
| 50 | + | |
| 51 | + void onStartTrackingTouch(VerticalSeekBar seekBar); | |
| 52 | + | |
| 53 | + void onStopTrackingTouch(VerticalSeekBar seekBar); | |
| 54 | + | |
| 55 | + void onRequestDisallowInterceptTouchEvent(boolean enable); | |
| 56 | + } | |
| 57 | + | |
| 58 | + private void initialize(Context context) { | |
| 59 | + LayoutParams params = new LayoutParams(mBGSize, LayoutParams.MATCH_PARENT); | |
| 60 | + params.gravity = Gravity.CENTER_HORIZONTAL; | |
| 61 | + mBg = new View(context); | |
| 62 | + addView(mBg, params); | |
| 63 | + | |
| 64 | + GradientDrawable drawable = new GradientDrawable(); | |
| 65 | + drawable.setColor(0xffbcbab8); | |
| 66 | + drawable.setCornerRadius(mBGRadius); | |
| 67 | + mBg.setBackgroundDrawable(drawable); | |
| 68 | + | |
| 69 | + params = new LayoutParams(mBGSize, 0); | |
| 70 | + params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; | |
| 71 | + mSecondaryProgressBg = new View(context); | |
| 72 | + addView(mSecondaryProgressBg, params); | |
| 73 | + | |
| 74 | + drawable = new GradientDrawable(); | |
| 75 | + drawable.setColor(0xffff5959); | |
| 76 | + drawable.setCornerRadius(mBGRadius); | |
| 77 | + mSecondaryProgressBg.setBackgroundDrawable(drawable); | |
| 78 | + | |
| 79 | + params = new LayoutParams(mBGSize, 0); | |
| 80 | + params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; | |
| 81 | + mProgressBg = new View(context); | |
| 82 | + addView(mProgressBg, params); | |
| 83 | + | |
| 84 | + drawable = new GradientDrawable(); | |
| 85 | + drawable.setColor(0xffff5959); | |
| 86 | + drawable.setCornerRadius(mBGRadius); | |
| 87 | + mProgressBg.setBackgroundDrawable(drawable); | |
| 88 | + | |
| 89 | + params = new LayoutParams(mCircleSize, mCircleSize); | |
| 90 | + params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; | |
| 91 | + mSlider = new FrameLayout(context); | |
| 92 | + addView(mSlider, params); | |
| 93 | + | |
| 94 | + drawable = new GradientDrawable(); | |
| 95 | + drawable.setColor(0xffdddddd); | |
| 96 | + drawable.setShape(GradientDrawable.OVAL); | |
| 97 | + | |
| 98 | + params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); | |
| 99 | + params.gravity = Gravity.CENTER; | |
| 100 | + mTextView = new TextView(context); | |
| 101 | + mTextView.setGravity(Gravity.CENTER); | |
| 102 | + mTextView.setTextSize(12); | |
| 103 | + mTextView.setIncludeFontPadding(false); | |
| 104 | + mTextView.setText("0"); | |
| 105 | + mTextView.setTextColor(0xff666666); | |
| 106 | + //mSlider.addView(mTextView, params); | |
| 107 | + | |
| 108 | + mSlider.setBackgroundDrawable(drawable); | |
| 109 | + | |
| 110 | + setClickable(true); | |
| 111 | + } | |
| 112 | + | |
| 113 | + public String getText() { | |
| 114 | + return mTextView.getText().toString(); | |
| 115 | + } | |
| 116 | + | |
| 117 | + public void setmText(String mText) { | |
| 118 | + mTextView.setText(mText); | |
| 119 | + } | |
| 120 | + | |
| 121 | + public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { | |
| 122 | + mOnSeekBarChangeListener = l; | |
| 123 | + } | |
| 124 | + | |
| 125 | + public void setThumb(Drawable d) { | |
| 126 | + mSlider.setBackgroundDrawable(d); | |
| 127 | + } | |
| 128 | + | |
| 129 | + public void setThumbSize(int w, int h) { | |
| 130 | + LayoutParams params = (LayoutParams) mSlider.getLayoutParams(); | |
| 131 | + params.width = w; | |
| 132 | + params.height = h; | |
| 133 | + mSlider.setLayoutParams(params); | |
| 134 | + } | |
| 135 | + | |
| 136 | + public void setProgressSize(int size) { | |
| 137 | + LayoutParams params = (LayoutParams) mProgressBg.getLayoutParams(); | |
| 138 | + params.width = size; | |
| 139 | + mProgressBg.setLayoutParams(params); | |
| 140 | + | |
| 141 | + params = (LayoutParams) mSecondaryProgressBg.getLayoutParams(); | |
| 142 | + params.width = size; | |
| 143 | + mSecondaryProgressBg.setLayoutParams(params); | |
| 144 | + | |
| 145 | + params = (LayoutParams) mBg.getLayoutParams(); | |
| 146 | + params.width = size; | |
| 147 | + mBg.setLayoutParams(params); | |
| 148 | + } | |
| 149 | + | |
| 150 | + public void setProgressBackground(Drawable drawable) { | |
| 151 | + mBg.setBackgroundDrawable(drawable); | |
| 152 | + } | |
| 153 | + | |
| 154 | + public void setProgressDrawable(Drawable drawable) { | |
| 155 | + mProgressBg.setBackgroundDrawable(drawable); | |
| 156 | + } | |
| 157 | + | |
| 158 | + public void setSecondaryProgressDrawable(Drawable drawable) { | |
| 159 | + mSecondaryProgressBg.setBackgroundDrawable(drawable); | |
| 160 | + } | |
| 161 | + | |
| 162 | + public void setMax(int max) { | |
| 163 | + mMax = max; | |
| 164 | + } | |
| 165 | + | |
| 166 | + public void setProgress(int progress) { | |
| 167 | + setProgress(progress, false); | |
| 168 | + } | |
| 169 | + | |
| 170 | + public int getProgress() { | |
| 171 | + return mProgress; | |
| 172 | + } | |
| 173 | + | |
| 174 | + private void setProgress(int progress, boolean fromUser) { | |
| 175 | + if (progress < 0) | |
| 176 | + progress = 0; | |
| 177 | + if (progress > mMax) | |
| 178 | + progress = mMax; | |
| 179 | + | |
| 180 | + if (mProgress != progress) { | |
| 181 | + mProgress = progress; | |
| 182 | + updateProgress(fromUser); | |
| 183 | + } | |
| 184 | + mTextView.setText(progress + ""); | |
| 185 | + } | |
| 186 | + | |
| 187 | + private void updateProgress(boolean fromUser) { | |
| 188 | + int paddingTop = getPaddingTop(); | |
| 189 | + int paddingBottom = getPaddingBottom(); | |
| 190 | + int h = getHeight() - paddingTop - paddingBottom; | |
| 191 | + if (h > 0) { | |
| 192 | + LayoutParams params = (LayoutParams) mProgressBg.getLayoutParams(); | |
| 193 | + params.height = h * mProgress / mMax; | |
| 194 | + mProgressBg.setLayoutParams(params); | |
| 195 | + | |
| 196 | + params = (LayoutParams) mSlider.getLayoutParams(); | |
| 197 | + params.bottomMargin = (h - params.height) * mProgress / mMax; | |
| 198 | + mSlider.setLayoutParams(params); | |
| 199 | + } | |
| 200 | + if (mOnSeekBarChangeListener != null) { | |
| 201 | + mOnSeekBarChangeListener.onProgressChanged(this, mProgress, fromUser); | |
| 202 | + } | |
| 203 | + } | |
| 204 | + | |
| 205 | + public void setSecondaryProgress(int progress) { | |
| 206 | + if (progress < 0) | |
| 207 | + progress = 0; | |
| 208 | + if (progress > mMax) | |
| 209 | + progress = mMax; | |
| 210 | + | |
| 211 | + mSecondaryProgress = progress; | |
| 212 | + updateSecondaryProgress(); | |
| 213 | + } | |
| 214 | + | |
| 215 | + private void updateSecondaryProgress() { | |
| 216 | + int paddingTop = getPaddingTop(); | |
| 217 | + int paddingBottom = getPaddingBottom(); | |
| 218 | + int h = getHeight() - paddingTop - paddingBottom; | |
| 219 | + if (h > 0) { | |
| 220 | + LayoutParams params = (LayoutParams) mSecondaryProgressBg.getLayoutParams(); | |
| 221 | + params.height = h * mSecondaryProgress / mMax; | |
| 222 | + mSecondaryProgressBg.setLayoutParams(params); | |
| 223 | + } | |
| 224 | + } | |
| 225 | + | |
| 226 | + | |
| 227 | + @Override | |
| 228 | + public boolean dispatchTouchEvent(MotionEvent ev) { | |
| 229 | + int action = ev.getAction(); | |
| 230 | + int y = (int) ev.getY(); | |
| 231 | + int paddingTop = getPaddingTop(); | |
| 232 | + int paddingBottom = getPaddingBottom(); | |
| 233 | + int h = getHeight() - paddingTop - paddingBottom; | |
| 234 | + if (y < paddingTop) { | |
| 235 | + y = paddingTop; | |
| 236 | + } | |
| 237 | + if (y > h + paddingTop) { | |
| 238 | + y = h + paddingTop; | |
| 239 | + } | |
| 240 | + y -= paddingTop; | |
| 241 | + if (action == MotionEvent.ACTION_DOWN) { | |
| 242 | + if (mOnSeekBarChangeListener != null) { | |
| 243 | + mOnSeekBarChangeListener.onRequestDisallowInterceptTouchEvent(true); | |
| 244 | + } | |
| 245 | + int progress = mMax * (h - y) / h; | |
| 246 | + if (progress != mProgress) { | |
| 247 | + if (mTrackingTouch == false) { | |
| 248 | + mTrackingTouch = true; | |
| 249 | + if (mOnSeekBarChangeListener != null) { | |
| 250 | + mOnSeekBarChangeListener.onStartTrackingTouch(this); | |
| 251 | + } | |
| 252 | + } | |
| 253 | + setProgress(progress, true); | |
| 254 | + } | |
| 255 | + } else if (action == MotionEvent.ACTION_MOVE) { | |
| 256 | + int progress = mMax * (h - y) / h; | |
| 257 | + if (progress != mProgress) { | |
| 258 | + if (mTrackingTouch == false) { | |
| 259 | + mTrackingTouch = true; | |
| 260 | + if (mOnSeekBarChangeListener != null) { | |
| 261 | + mOnSeekBarChangeListener.onStartTrackingTouch(this); | |
| 262 | + } | |
| 263 | + } | |
| 264 | + setProgress(progress, true); | |
| 265 | + } | |
| 266 | + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { | |
| 267 | + if (mOnSeekBarChangeListener != null) { | |
| 268 | + mOnSeekBarChangeListener.onRequestDisallowInterceptTouchEvent(false); | |
| 269 | + } | |
| 270 | + mTrackingTouch = false; | |
| 271 | + if (mOnSeekBarChangeListener != null) { | |
| 272 | + mOnSeekBarChangeListener.onStopTrackingTouch(this); | |
| 273 | + } | |
| 274 | + } | |
| 275 | + return super.dispatchTouchEvent(ev); | |
| 276 | + } | |
| 277 | + | |
| 278 | + @Override | |
| 279 | + protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
| 280 | + updateProgress(false); | |
| 281 | + //updateSecondaryProgress(); | |
| 282 | + super.onSizeChanged(w, h, oldw, oldh); | |
| 283 | + } | |
| 284 | + | |
| 285 | +} | ... | ... |
app/src/main/res/anim/slide_in_bottom.xml
0 → 100644
app/src/main/res/anim/slide_in_top.xml
0 → 100644
app/src/main/res/anim/slide_left_in.xml
0 → 100644
app/src/main/res/anim/slide_left_out.xml
0 → 100644
app/src/main/res/anim/slide_out_bottom.xml
0 → 100644
app/src/main/res/anim/slide_out_top.xml
0 → 100644
app/src/main/res/anim/slide_right_in.xml
0 → 100644
app/src/main/res/anim/slide_right_out.xml
0 → 100644
app/src/main/res/anim/slide_stand.xml
0 → 100644
app/src/main/res/drawable-xxhdpi/close.png
0 → 100644
498 Bytes
app/src/main/res/drawable-xxhdpi/exposure.png
0 → 100644
1.04 KB
app/src/main/res/drawable-xxhdpi/flash.png
0 → 100644
750 Bytes
app/src/main/res/drawable-xxhdpi/float_back.png
0 → 100644
1.2 KB
app/src/main/res/drawable-xxhdpi/float_close.png
0 → 100644
1.1 KB
app/src/main/res/drawable-xxhdpi/float_view.png
0 → 100644
688 Bytes
app/src/main/res/drawable-xxhdpi/icon_record_close_off.png deleted
100644 → 0
5.67 KB
app/src/main/res/drawable-xxhdpi/icon_record_flash_off.png deleted
100644 → 0
3.77 KB
app/src/main/res/drawable-xxhdpi/icon_record_flip_off.png deleted
100644 → 0
4.03 KB
app/src/main/res/drawable-xxhdpi/switch_camera.png
0 → 100644
975 Bytes
app/src/main/res/layout/activity_float.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + android:layout_width="match_parent" | |
| 5 | + android:layout_height="match_parent" | |
| 6 | + android:background="#00000000" | |
| 7 | + tools:context="com.cnlive.demo.stream.FloatActivity"> | |
| 8 | + | |
| 9 | + <LinearLayout | |
| 10 | + android:layout_width="match_parent" | |
| 11 | + android:layout_height="48dp" | |
| 12 | + android:background="@color/colorAccent"> | |
| 13 | + | |
| 14 | + <ImageView | |
| 15 | + android:id="@+id/float_back" | |
| 16 | + android:layout_width="25dp" | |
| 17 | + android:layout_height="25dp" | |
| 18 | + android:layout_alignParentLeft="true" | |
| 19 | + android:layout_alignParentTop="true" | |
| 20 | + android:layout_gravity="center_vertical" | |
| 21 | + android:layout_marginLeft="10dp" | |
| 22 | + android:scaleType="centerCrop" | |
| 23 | + android:src="@drawable/float_back" /> | |
| 24 | + | |
| 25 | + </LinearLayout> | |
| 26 | +</RelativeLayout> | ... | ... |
app/src/main/res/layout/activity_stream.xml
| ... | ... | @@ -19,6 +19,18 @@ |
| 19 | 19 | android:layout_alignParentBottom="true" |
| 20 | 20 | android:layout_alignParentTop="true" /> |
| 21 | 21 | |
| 22 | + <com.cnlive.demo.stream.VerticalSeekBar | |
| 23 | + android:id="@+id/exposure_seekBar" | |
| 24 | + android:layout_width="wrap_content" | |
| 25 | + android:layout_height="200dp" | |
| 26 | + android:layout_alignParentRight="true" | |
| 27 | + android:layout_marginRight="20dp" | |
| 28 | + android:layout_marginTop="100dp" | |
| 29 | + android:progressBackgroundTint="#9b9b9b" | |
| 30 | + android:progressTint="@color/colorAccent" | |
| 31 | + android:thumbTint="#C0303030" | |
| 32 | + android:visibility="gone" /> | |
| 33 | + | |
| 22 | 34 | <include |
| 23 | 35 | android:id="@+id/actionbar" |
| 24 | 36 | layout="@layout/titlebar" | ... | ... |
app/src/main/res/layout/bottombar.xml
| ... | ... | @@ -21,6 +21,56 @@ |
| 21 | 21 | android:layout_marginBottom="10dp" |
| 22 | 22 | android:orientation="horizontal"> |
| 23 | 23 | |
| 24 | + <Button | |
| 25 | + android:id="@+id/audio_mix" | |
| 26 | + android:layout_width="0dp" | |
| 27 | + android:layout_height="wrap_content" | |
| 28 | + android:layout_marginLeft="10dp" | |
| 29 | + android:layout_weight="1" | |
| 30 | + android:background="#11123456" | |
| 31 | + android:text="音频滤镜" | |
| 32 | + android:textColor="@android:color/white" | |
| 33 | + android:textSize="14sp" /> | |
| 34 | + | |
| 35 | + <Button | |
| 36 | + android:id="@+id/bgm_filter" | |
| 37 | + android:layout_width="0dp" | |
| 38 | + android:layout_height="wrap_content" | |
| 39 | + android:layout_marginLeft="10dp" | |
| 40 | + android:layout_weight="1" | |
| 41 | + android:background="#11123456" | |
| 42 | + android:text="bgm升降调" | |
| 43 | + android:textColor="@android:color/white" /> | |
| 44 | + | |
| 45 | + <Button | |
| 46 | + android:id="@+id/click_to_capture_screenshot" | |
| 47 | + android:layout_width="0dp" | |
| 48 | + android:layout_height="wrap_content" | |
| 49 | + android:layout_marginLeft="10dp" | |
| 50 | + android:layout_weight="1" | |
| 51 | + android:background="#11123456" | |
| 52 | + android:text="截屏" | |
| 53 | + android:textColor="@android:color/white" /> | |
| 54 | + | |
| 55 | + <CheckBox | |
| 56 | + android:id="@+id/bg_img" | |
| 57 | + android:layout_width="0dp" | |
| 58 | + android:layout_height="wrap_content" | |
| 59 | + android:layout_gravity="center" | |
| 60 | + android:layout_marginLeft="10dp" | |
| 61 | + android:layout_weight="1" | |
| 62 | + android:background="#11123456" | |
| 63 | + android:text="背景图推流" | |
| 64 | + android:textColor="@android:color/white" | |
| 65 | + android:textSize="14sp" /> | |
| 66 | + </LinearLayout> | |
| 67 | + | |
| 68 | + <LinearLayout | |
| 69 | + android:layout_width="match_parent" | |
| 70 | + android:layout_height="wrap_content" | |
| 71 | + android:layout_marginBottom="10dp" | |
| 72 | + android:orientation="horizontal"> | |
| 73 | + | |
| 24 | 74 | <CheckBox |
| 25 | 75 | android:id="@+id/watermark" |
| 26 | 76 | android:layout_width="0dp" |
| ... | ... | @@ -126,7 +176,7 @@ |
| 126 | 176 | android:layout_weight="1" |
| 127 | 177 | android:background="#11123456" |
| 128 | 178 | android:checked="true" |
| 129 | - android:text="美颜" | |
| 179 | + android:text="美颜滤镜" | |
| 130 | 180 | android:textColor="@android:color/white" |
| 131 | 181 | android:textSize="14sp"></CheckBox> |
| 132 | 182 | ... | ... |
app/src/main/res/layout/float_preview.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<com.cnlive.demo.stream.FloatView | |
| 3 | + xmlns:android="http://schemas.android.com/apk/res/android" | |
| 4 | + android:layout_width="wrap_content" | |
| 5 | + android:layout_height="wrap_content" | |
| 6 | + android:orientation="horizontal"> | |
| 7 | + | |
| 8 | +</com.cnlive.demo.stream.FloatView> | |
| 0 | 9 | \ No newline at end of file | ... | ... |
app/src/main/res/layout/titlebar.xml
| ... | ... | @@ -17,36 +17,56 @@ |
| 17 | 17 | android:textSize="19sp" /> |
| 18 | 18 | |
| 19 | 19 | <ImageView |
| 20 | + android:id="@+id/add" | |
| 21 | + android:layout_width="22dp" | |
| 22 | + android:layout_height="22dp" | |
| 23 | + android:layout_gravity="center_vertical" | |
| 24 | + android:layout_marginRight="10dp" | |
| 25 | + android:layout_marginTop="8dp" | |
| 26 | + android:layout_toLeftOf="@+id/exposure" | |
| 27 | + android:background="@drawable/float_view" /> | |
| 28 | + | |
| 29 | + <ImageView | |
| 30 | + android:id="@+id/exposure" | |
| 31 | + android:layout_width="22dp" | |
| 32 | + android:layout_height="22dp" | |
| 33 | + android:layout_gravity="center_vertical" | |
| 34 | + android:layout_marginRight="10dp" | |
| 35 | + android:layout_marginTop="8dp" | |
| 36 | + android:layout_toLeftOf="@+id/flash" | |
| 37 | + android:background="@drawable/exposure" /> | |
| 38 | + | |
| 39 | + <ImageView | |
| 20 | 40 | android:id="@+id/flash" |
| 21 | - android:layout_width="wrap_content" | |
| 22 | - android:layout_height="wrap_content" | |
| 41 | + android:layout_width="22dp" | |
| 42 | + android:layout_height="22dp" | |
| 23 | 43 | android:layout_gravity="center_vertical" |
| 24 | 44 | android:layout_marginRight="12dp" |
| 25 | 45 | android:layout_marginTop="8dp" |
| 26 | 46 | android:layout_toLeftOf="@+id/switch_cam" |
| 27 | - android:background="@drawable/icon_record_flash_off"></ImageView> | |
| 47 | + android:background="@drawable/flash"></ImageView> | |
| 28 | 48 | |
| 29 | 49 | <ImageView |
| 30 | 50 | android:id="@+id/switch_cam" |
| 31 | - android:layout_width="wrap_content" | |
| 32 | - android:layout_height="wrap_content" | |
| 51 | + android:layout_width="22dp" | |
| 52 | + android:layout_height="22dp" | |
| 33 | 53 | android:layout_alignParentTop="true" |
| 34 | 54 | android:layout_gravity="center_vertical" |
| 35 | 55 | android:layout_marginRight="12dp" |
| 36 | 56 | android:layout_marginTop="8dp" |
| 37 | 57 | android:layout_toLeftOf="@+id/backoff" |
| 38 | - android:background="@drawable/icon_record_flip_off" /> | |
| 58 | + android:background="@drawable/switch_camera" /> | |
| 39 | 59 | |
| 40 | 60 | <ImageView |
| 41 | 61 | android:id="@+id/backoff" |
| 42 | - android:layout_width="wrap_content" | |
| 43 | - android:layout_height="wrap_content" | |
| 62 | + android:layout_width="22dp" | |
| 63 | + android:layout_height="22dp" | |
| 44 | 64 | android:layout_alignParentEnd="true" |
| 45 | 65 | android:layout_alignParentRight="true" |
| 46 | 66 | android:layout_alignParentTop="true" |
| 47 | 67 | android:layout_gravity="center_vertical" |
| 48 | 68 | android:layout_marginRight="2dp" |
| 49 | 69 | android:layout_marginTop="8dp" |
| 50 | - android:background="@drawable/icon_record_close_off" /> | |
| 70 | + android:background="@drawable/close" /> | |
| 51 | 71 | |
| 52 | 72 | </RelativeLayout> |
| 53 | 73 | \ No newline at end of file | ... | ... |
app/src/main/res/raw/beep.ogg
0 → 100644
No preview for this file type
app/src/main/res/raw/fragment_sharder.glsl
0 → 100644
app/src/main/res/raw/fugu.acv
0 → 100644
No preview for this file type
app/src/main/res/raw/jiaopian.acv
0 → 100644
No preview for this file type