├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ ├── LiteAVSDK_Professional_4.6.4218.aar │ ├── gson-2.3.1.jar │ └── liteimsdk-release.aar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zjp │ │ └── tencentvideo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zjp │ │ │ └── tencentvideo │ │ │ ├── BiliDanmukuParser.java │ │ │ ├── DanmakuController.java │ │ │ ├── TecentVideoTestApplication.java │ │ │ ├── beautysettings │ │ │ ├── BeautyDialogFragment.java │ │ │ └── TCHorizontalScrollView.java │ │ │ ├── ui │ │ │ ├── LiveActivity.java │ │ │ ├── MainActivity.java │ │ │ └── RTMPActivity.java │ │ │ ├── utils │ │ │ ├── BitmapUtils.java │ │ │ ├── TCFrequeControl.java │ │ │ └── TCUtils.java │ │ │ └── view │ │ │ ├── TCInputTextMsgDialog.java │ │ │ └── like │ │ │ ├── TCAbstractPathAnimator.java │ │ │ ├── TCHeartLayout.java │ │ │ ├── TCHeartView.java │ │ │ └── TCPathAnimator.java │ └── res │ │ ├── anim │ │ └── load_progress_animation.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── again.png │ │ ├── barrage_slider_off.png │ │ ├── barrage_slider_on.png │ │ ├── beautiful.png │ │ ├── beautiful_press.png │ │ ├── beauty.png │ │ ├── btn_back_gray.png │ │ ├── camera_change.png │ │ ├── close.png │ │ ├── face.png │ │ ├── icon_beauty_png.png │ │ ├── icon_like_png.png │ │ ├── icon_log_off.png │ │ ├── icon_message_png.png │ │ ├── icon_share_png.png │ │ ├── icon_switch_camera_on.png │ │ ├── linkmic_on_normal.png │ │ ├── loading_circle_progress.png │ │ ├── play_seekbar_icon.png │ │ ├── text_accent_gray_color.xml │ │ └── video.png │ │ ├── drawable-xxhdpi │ │ ├── drawable_face_beauty.xml │ │ ├── drawable_face_filter.xml │ │ ├── drawable_face_green.xml │ │ ├── drawable_face_move.xml │ │ ├── fennen.png │ │ ├── filter.png │ │ ├── filter_fennen.png │ │ ├── filter_huaijiu.png │ │ ├── filter_landiao.png │ │ ├── filter_langman.png │ │ ├── filter_press.png │ │ ├── filter_qingliang.png │ │ ├── filter_qingxin.png │ │ ├── filter_rixi.png │ │ ├── filter_selected.png │ │ ├── filter_weimei.png │ │ ├── filter_white.png │ │ ├── greens.png │ │ ├── greens_1.png │ │ ├── greens_2.png │ │ ├── greens_no.png │ │ ├── greens_press.png │ │ ├── greens_selected.png │ │ ├── heart0.png │ │ ├── heart1.png │ │ ├── heart10.png │ │ ├── heart11.png │ │ ├── heart2.png │ │ ├── heart3.png │ │ ├── heart4.png │ │ ├── heart5.png │ │ ├── heart6.png │ │ ├── heart7.png │ │ ├── heart8.png │ │ ├── heart9.png │ │ ├── huaijiu.png │ │ ├── landiao.png │ │ ├── landscape.png │ │ ├── langman.png │ │ ├── linkmic_start.png │ │ ├── liveshow_item_dot.xml │ │ ├── move.png │ │ ├── move_press.png │ │ ├── orginal.png │ │ ├── play_pause.png │ │ ├── play_start.png │ │ ├── portrait.png │ │ ├── progress_bg.png │ │ ├── progress_front.png │ │ ├── progress_primary.png │ │ ├── qingliang.png │ │ ├── qingxin.png │ │ ├── rixi.png │ │ ├── start_record.png │ │ └── weimei.png │ │ ├── drawable │ │ ├── btn_send_message.xml │ │ ├── ic_launcher_background.xml │ │ ├── play_seekbar_progress.xml │ │ ├── progress_bg.9.png │ │ ├── pusher_info_background.xml │ │ ├── record_progress.xml │ │ └── seekbar_progress_drawable.xml │ │ ├── layout │ │ ├── activity_live.xml │ │ ├── activity_rtmp.xml │ │ ├── beauty_pannel.xml │ │ ├── dialog_input_text.xml │ │ ├── filter_layout.xml │ │ ├── fragment_beauty_area.xml │ │ ├── green_layout.xml │ │ ├── layout_live_pusher_info.xml │ │ ├── layout_loading_progress.xml │ │ ├── layout_periscope.xml │ │ └── layout_progress_bar.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 │ │ └── pause_publish.jpg │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ └── comments.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zjp │ └── tencentvideo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── 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 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 腾讯视频直播--推流 3 |

具体介绍参考博客

4 |

腾讯视频直播01-直播常见协议和集成sdk:https://www.jianshu.com/p/50d6cdb83018

5 |

腾讯视频直播02-推流:https://www.jianshu.com/p/914bdd9afdb0

6 |

腾讯视频直播03-直播:https://www.jianshu.com/p/608ebdfb238b

7 | -------------------------------------------------------------------------------- /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.zjp.tencentvideo" 7 | minSdkVersion 15 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | 13 | ndk { 14 | abiFilters "armeabi", "armeabi-v7a" 15 | // 如果您使用的是商业版,只能使用 armeabi 架构,即: 16 | // abiFilters "armeabi", 17 | } 18 | 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(include: ['*.jar'], dir: 'libs') 31 | implementation 'com.android.support:appcompat-v7:27.1.1' 32 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'com.android. support.test:runner:1.0.2' 35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 36 | compile(name: 'LiteAVSDK_Professional_4.6.4218', ext: 'aar') 37 | compile(name: 'liteimsdk-release', ext: 'aar') 38 | compile 'com.android.support:multidex:1.0.1' 39 | implementation 'com.android.support:design:27.1.1' 40 | implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25' 41 | implementation 'com.github.yyued:SVGAPlayer-Android:2.0.3' 42 | compile 'com.squareup.okhttp3:logging-interceptor:3.8.1' 43 | implementation files('libs/gson-2.3.1.jar') 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/libs/LiteAVSDK_Professional_4.6.4218.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/libs/LiteAVSDK_Professional_4.6.4218.aar -------------------------------------------------------------------------------- /app/libs/gson-2.3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/libs/gson-2.3.1.jar -------------------------------------------------------------------------------- /app/libs/liteimsdk-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/libs/liteimsdk-release.aar -------------------------------------------------------------------------------- /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/zjp/tencentvideo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo; 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.zjp.tencentvideo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/BiliDanmukuParser.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo; 2 | 3 | import android.graphics.Color; 4 | import android.text.TextUtils; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.xml.sax.Attributes; 9 | import org.xml.sax.InputSource; 10 | import org.xml.sax.SAXException; 11 | import org.xml.sax.XMLReader; 12 | import org.xml.sax.helpers.DefaultHandler; 13 | import org.xml.sax.helpers.XMLReaderFactory; 14 | 15 | import java.io.IOException; 16 | import java.util.Locale; 17 | 18 | import master.flame.danmaku.danmaku.model.AlphaValue; 19 | import master.flame.danmaku.danmaku.model.BaseDanmaku; 20 | import master.flame.danmaku.danmaku.model.Duration; 21 | import master.flame.danmaku.danmaku.model.IDisplayer; 22 | import master.flame.danmaku.danmaku.model.SpecialDanmaku; 23 | import master.flame.danmaku.danmaku.model.android.DanmakuFactory; 24 | import master.flame.danmaku.danmaku.model.android.Danmakus; 25 | import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; 26 | import master.flame.danmaku.danmaku.parser.android.AndroidFileSource; 27 | import master.flame.danmaku.danmaku.util.DanmakuUtils; 28 | 29 | import static master.flame.danmaku.danmaku.model.IDanmakus.ST_BY_TIME; 30 | 31 | /** 32 | * @author wapchief 33 | * @date 2018/3/20 34 | */ 35 | 36 | public class BiliDanmukuParser extends BaseDanmakuParser { 37 | 38 | static { 39 | System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); 40 | } 41 | 42 | protected float mDispScaleX; 43 | protected float mDispScaleY; 44 | 45 | @Override 46 | public Danmakus parse() { 47 | 48 | if (mDataSource != null) { 49 | AndroidFileSource source = (AndroidFileSource) mDataSource; 50 | try { 51 | XMLReader xmlReader = XMLReaderFactory.createXMLReader(); 52 | XmlContentHandler contentHandler = new XmlContentHandler(); 53 | xmlReader.setContentHandler(contentHandler); 54 | xmlReader.parse(new InputSource(source.data())); 55 | return contentHandler.getResult(); 56 | } catch (SAXException e) { 57 | e.printStackTrace(); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } 61 | 62 | } 63 | 64 | return null; 65 | } 66 | 67 | public class XmlContentHandler extends DefaultHandler { 68 | 69 | private static final String TRUE_STRING = "true"; 70 | 71 | public Danmakus result; 72 | 73 | public BaseDanmaku item = null; 74 | 75 | public boolean completed = false; 76 | 77 | public int index = 0; 78 | 79 | public Danmakus getResult() { 80 | return result; 81 | } 82 | 83 | @Override 84 | public void startDocument() throws SAXException { 85 | result = new Danmakus(ST_BY_TIME, false, mContext.getBaseComparator()); 86 | } 87 | 88 | @Override 89 | public void endDocument() throws SAXException { 90 | completed = true; 91 | } 92 | 93 | @Override 94 | public void startElement(String uri, String localName, String qName, Attributes attributes) 95 | throws SAXException { 96 | String tagName = localName.length() != 0 ? localName : qName; 97 | tagName = tagName.toLowerCase(Locale.getDefault()).trim(); 98 | if (tagName.equals("d")) { 99 | // 我从未见过如此厚颜无耻之猴 100 | // 0:时间(弹幕出现时间) 101 | // 1:类型(1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕) 102 | // 2:字号 103 | // 3:颜色 104 | // 4:时间戳 ? 105 | // 5:弹幕池id 106 | // 6:用户hash 107 | // 7:弹幕id 108 | String pValue = attributes.getValue("p"); 109 | // parse p value to danmaku 110 | String[] values = pValue.split(","); 111 | if (values.length > 0) { 112 | long time = (long) (parseFloat(values[0]) * 1000); // 出现时间 113 | int type = parseInteger(values[1]); // 弹幕类型 114 | float textSize = parseFloat(values[2]); // 字体大小 115 | int color = (int) ((0x00000000ff000000 | parseLong(values[3])) & 0x00000000ffffffff); // 颜色 116 | // int poolType = parseInteger(values[5]); // 弹幕池类型(忽略 117 | item = mContext.mDanmakuFactory.createDanmaku(type, mContext); 118 | if (item != null) { 119 | item.setTime(time); 120 | item.textSize = textSize * (mDispDensity - 0.6f); 121 | item.textColor = color; 122 | item.textShadowColor = color <= Color.BLACK ? Color.WHITE : Color.BLACK; 123 | } 124 | } 125 | } 126 | } 127 | 128 | @Override 129 | public void endElement(String uri, String localName, String qName) throws SAXException { 130 | if (item != null && item.text != null) { 131 | if (item.duration != null) { 132 | String tagName = localName.length() != 0 ? localName : qName; 133 | if (tagName.equalsIgnoreCase("d")) { 134 | item.setTimer(mTimer); 135 | item.flags = mContext.mGlobalFlagValues; 136 | Object lock = result.obtainSynchronizer(); 137 | synchronized (lock) { 138 | result.addItem(item); 139 | } 140 | } 141 | } 142 | item = null; 143 | } 144 | } 145 | 146 | @Override 147 | public void characters(char[] ch, int start, int length) { 148 | if (item != null) { 149 | DanmakuUtils.fillText(item, decodeXmlString(new String(ch, start, length))); 150 | item.index = index++; 151 | 152 | // initial specail danmaku data 153 | String text = String.valueOf(item.text).trim(); 154 | if (item.getType() == BaseDanmaku.TYPE_SPECIAL && text.startsWith("[") 155 | && text.endsWith("]")) { 156 | //text = text.substring(1, text.length() - 1); 157 | String[] textArr = null;//text.split(",", -1); 158 | try { 159 | JSONArray jsonArray = new JSONArray(text); 160 | textArr = new String[jsonArray.length()]; 161 | for (int i = 0; i < textArr.length; i++) { 162 | textArr[i] = jsonArray.getString(i); 163 | } 164 | } catch (JSONException e) { 165 | e.printStackTrace(); 166 | } 167 | 168 | if (textArr == null || textArr.length < 5 || TextUtils.isEmpty(textArr[4])) { 169 | item = null; 170 | return; 171 | } 172 | DanmakuUtils.fillText(item, textArr[4]); 173 | float beginX = parseFloat(textArr[0]); 174 | float beginY = parseFloat(textArr[1]); 175 | float endX = beginX; 176 | float endY = beginY; 177 | String[] alphaArr = textArr[2].split("-"); 178 | int beginAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[0])); 179 | int endAlpha = beginAlpha; 180 | if (alphaArr.length > 1) { 181 | endAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[1])); 182 | } 183 | long alphaDuraion = (long) (parseFloat(textArr[3]) * 1000); 184 | long translationDuration = alphaDuraion; 185 | long translationStartDelay = 0; 186 | float rotateY = 0, rotateZ = 0; 187 | if (textArr.length >= 7) { 188 | rotateZ = parseFloat(textArr[5]); 189 | rotateY = parseFloat(textArr[6]); 190 | } 191 | if (textArr.length >= 11) { 192 | endX = parseFloat(textArr[7]); 193 | endY = parseFloat(textArr[8]); 194 | if (!"".equals(textArr[9])) { 195 | translationDuration = parseInteger(textArr[9]); 196 | } 197 | if (!"".equals(textArr[10])) { 198 | translationStartDelay = (long) (parseFloat(textArr[10])); 199 | } 200 | } 201 | if (isPercentageNumber(textArr[0])) { 202 | beginX *= DanmakuFactory.BILI_PLAYER_WIDTH; 203 | } 204 | if (isPercentageNumber(textArr[1])) { 205 | beginY *= DanmakuFactory.BILI_PLAYER_HEIGHT; 206 | } 207 | if (textArr.length >= 8 && isPercentageNumber(textArr[7])) { 208 | endX *= DanmakuFactory.BILI_PLAYER_WIDTH; 209 | } 210 | if (textArr.length >= 9 && isPercentageNumber(textArr[8])) { 211 | endY *= DanmakuFactory.BILI_PLAYER_HEIGHT; 212 | } 213 | item.duration = new Duration(alphaDuraion); 214 | item.rotationZ = rotateZ; 215 | item.rotationY = rotateY; 216 | mContext.mDanmakuFactory.fillTranslationData(item, beginX, 217 | beginY, endX, endY, translationDuration, translationStartDelay, mDispScaleX, mDispScaleY); 218 | mContext.mDanmakuFactory.fillAlphaData(item, beginAlpha, endAlpha, alphaDuraion); 219 | 220 | if (textArr.length >= 12) { 221 | // 是否有描边 222 | if (!TextUtils.isEmpty(textArr[11]) && TRUE_STRING.equalsIgnoreCase(textArr[11])) { 223 | item.textShadowColor = Color.TRANSPARENT; 224 | } 225 | } 226 | if (textArr.length >= 13) { 227 | //TODO 字体 textArr[12] 228 | } 229 | if (textArr.length >= 14) { 230 | // Linear.easeIn or Quadratic.easeOut 231 | ((SpecialDanmaku) item).isQuadraticEaseOut = ("0".equals(textArr[13])); 232 | } 233 | if (textArr.length >= 15) { 234 | // 路径数据 235 | if (!"".equals(textArr[14])) { 236 | String motionPathString = textArr[14].substring(1); 237 | if (!TextUtils.isEmpty(motionPathString)) { 238 | String[] pointStrArray = motionPathString.split("L"); 239 | if (pointStrArray.length > 0) { 240 | float[][] points = new float[pointStrArray.length][2]; 241 | for (int i = 0; i < pointStrArray.length; i++) { 242 | String[] pointArray = pointStrArray[i].split(","); 243 | if (pointArray.length >= 2) { 244 | points[i][0] = parseFloat(pointArray[0]); 245 | points[i][1] = parseFloat(pointArray[1]); 246 | } 247 | } 248 | mContext.mDanmakuFactory.fillLinePathData(item, points, mDispScaleX, 249 | mDispScaleY); 250 | } 251 | } 252 | } 253 | } 254 | } 255 | 256 | } 257 | } 258 | 259 | private String decodeXmlString(String title) { 260 | if (title.contains("&")) { 261 | title = title.replace("&", "&"); 262 | } 263 | if (title.contains(""")) { 264 | title = title.replace(""", "\""); 265 | } 266 | if (title.contains(">")) { 267 | title = title.replace(">", ">"); 268 | } 269 | if (title.contains("<")) { 270 | title = title.replace("<", "<"); 271 | } 272 | return title; 273 | } 274 | 275 | } 276 | 277 | private boolean isPercentageNumber(String number) { 278 | //return number >= 0f && number <= 1f; 279 | return number != null && number.contains("."); 280 | } 281 | 282 | private float parseFloat(String floatStr) { 283 | try { 284 | return Float.parseFloat(floatStr); 285 | } catch (NumberFormatException e) { 286 | return 0.0f; 287 | } 288 | } 289 | 290 | private int parseInteger(String intStr) { 291 | try { 292 | return Integer.parseInt(intStr); 293 | } catch (NumberFormatException e) { 294 | return 0; 295 | } 296 | } 297 | 298 | private long parseLong(String longStr) { 299 | try { 300 | return Long.parseLong(longStr); 301 | } catch (NumberFormatException e) { 302 | return 0; 303 | } 304 | } 305 | 306 | @Override 307 | public BaseDanmakuParser setDisplayer(IDisplayer disp) { 308 | super.setDisplayer(disp); 309 | mDispScaleX = mDispWidth / DanmakuFactory.BILI_PLAYER_WIDTH; 310 | mDispScaleY = mDispHeight / DanmakuFactory.BILI_PLAYER_HEIGHT; 311 | return this; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/DanmakuController.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.drawable.BitmapDrawable; 8 | import android.graphics.drawable.Drawable; 9 | import android.text.Spannable; 10 | import android.text.SpannableStringBuilder; 11 | import android.text.Spanned; 12 | import android.text.TextPaint; 13 | import android.text.style.BackgroundColorSpan; 14 | import android.text.style.ImageSpan; 15 | import android.util.Log; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.net.MalformedURLException; 20 | import java.net.URL; 21 | import java.net.URLConnection; 22 | import java.util.HashMap; 23 | import java.util.Timer; 24 | import java.util.TimerTask; 25 | 26 | import master.flame.danmaku.controller.IDanmakuView; 27 | import master.flame.danmaku.danmaku.loader.ILoader; 28 | import master.flame.danmaku.danmaku.loader.IllegalDataException; 29 | import master.flame.danmaku.danmaku.loader.android.DanmakuLoaderFactory; 30 | import master.flame.danmaku.danmaku.model.BaseDanmaku; 31 | import master.flame.danmaku.danmaku.model.DanmakuTimer; 32 | import master.flame.danmaku.danmaku.model.IDanmakus; 33 | import master.flame.danmaku.danmaku.model.IDisplayer; 34 | import master.flame.danmaku.danmaku.model.android.BaseCacheStuffer; 35 | import master.flame.danmaku.danmaku.model.android.DanmakuContext; 36 | import master.flame.danmaku.danmaku.model.android.Danmakus; 37 | import master.flame.danmaku.danmaku.model.android.SpannedCacheStuffer; 38 | import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; 39 | import master.flame.danmaku.danmaku.parser.IDataSource; 40 | import master.flame.danmaku.danmaku.util.IOUtils; 41 | import master.flame.danmaku.danmaku.util.SystemClock; 42 | import master.flame.danmaku.ui.widget.DanmakuView; 43 | 44 | /** 45 | * @author wapchief 46 | * @date 2018/3/21 47 | */ 48 | 49 | public class DanmakuController { 50 | /** 51 | * 弹幕 52 | */ 53 | private DanmakuContext mContext; 54 | private DanmakuView mDanmakuView; 55 | private Context content; 56 | BaseDanmakuParser mParser; 57 | public DanmakuController(DanmakuView danmakuView, Context content) { 58 | mDanmakuView = danmakuView; 59 | this.content = content; 60 | } 61 | 62 | /**初始化弹幕*/ 63 | public void initDanmaKu() { 64 | 65 | // 设置最大显示行数 66 | HashMap maxLinesPair = new HashMap(); 67 | maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 5); // 滚动弹幕最大显示5行 68 | // 设置是否禁止重叠 69 | HashMap overlappingEnablePair = new HashMap(); 70 | overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true); 71 | overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true); 72 | 73 | mContext = DanmakuContext.create(); 74 | mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3).setDuplicateMergingEnabled(false).setScrollSpeedFactor(1.2f).setScaleTextSize(1.2f) 75 | .setCacheStuffer(new SpannedCacheStuffer(), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer 76 | // .setCacheStuffer(new BackgroundCacheStuffer()) // 绘制背景使用BackgroundCacheStuffer 77 | .setMaximumLines(maxLinesPair) 78 | .preventOverlapping(overlappingEnablePair).setDanmakuMargin(40); 79 | if (mDanmakuView != null) { 80 | //本地弹幕资源 81 | mParser = createParser(content.getResources().openRawResource(R.raw.comments)); 82 | mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() { 83 | @Override 84 | public void updateTimer(DanmakuTimer timer) { 85 | } 86 | 87 | @Override 88 | public void drawingFinished() { 89 | 90 | } 91 | 92 | @Override 93 | public void danmakuShown(BaseDanmaku danmaku) { 94 | // Log.d("DFM", "danmakuShown(): text=" + danmaku.text); 95 | } 96 | 97 | @Override 98 | public void prepared() { 99 | mDanmakuView.start(); 100 | } 101 | }); 102 | mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() { 103 | 104 | @Override 105 | public boolean onDanmakuClick(IDanmakus danmakus) { 106 | Log.d("DFM", "onDanmakuClick: danmakus size:" + danmakus.size()); 107 | BaseDanmaku latest = danmakus.last(); 108 | if (null != latest) { 109 | Log.d("DFM", "onDanmakuClick: text of latest danmaku:" + latest.text); 110 | return true; 111 | } 112 | return false; 113 | } 114 | 115 | @Override 116 | public boolean onDanmakuLongClick(IDanmakus danmakus) { 117 | return false; 118 | } 119 | 120 | @Override 121 | public boolean onViewClick(IDanmakuView view) { 122 | // mMediaController.setVisibility(View.VISIBLE); 123 | return false; 124 | } 125 | }); 126 | mDanmakuView.prepare(mParser, mContext); 127 | mDanmakuView.showFPS(false); 128 | mDanmakuView.enableDanmakuDrawingCache(true); 129 | } 130 | 131 | // if (mVideoView != null) { 132 | // mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 133 | // @Override 134 | // public void onPrepared(MediaPlayer mediaPlayer) { 135 | // mediaPlayer.start(); 136 | // } 137 | // }); 138 | // mVideoView.setVideoPath(Environment.getExternalStorageDirectory() + "/1.flv"); 139 | // } 140 | } 141 | 142 | private void addDanmaku(boolean islive) { 143 | BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); 144 | if (danmaku == null || mDanmakuView == null) { 145 | return; 146 | } 147 | // for(int i=0;i<100;i++){ 148 | // } 149 | danmaku.text = "这是一条弹幕" + System.nanoTime(); 150 | danmaku.padding = 5; 151 | danmaku.priority = 0; // 可能会被各种过滤器过滤并隐藏显示 152 | danmaku.isLive = islive; 153 | danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); 154 | danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); 155 | danmaku.textColor = Color.RED; 156 | danmaku.textShadowColor = Color.WHITE; 157 | // danmaku.underlineColor = Color.GREEN; 158 | danmaku.borderColor = Color.GREEN; 159 | mDanmakuView.addDanmaku(danmaku); 160 | 161 | } 162 | 163 | /** 164 | * 绘制背景(自定义弹幕样式) 165 | */ 166 | private static class BackgroundCacheStuffer extends SpannedCacheStuffer { 167 | // 通过扩展SimpleTextCacheStuffer或SpannedCacheStuffer个性化你的弹幕样式 168 | final Paint paint = new Paint(); 169 | 170 | @Override 171 | public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { 172 | danmaku.padding = 10; // 在背景绘制模式下增加padding 173 | super.measure(danmaku, paint, fromWorkerThread); 174 | } 175 | 176 | @Override 177 | public void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) { 178 | paint.setColor(0x8125309b); 179 | canvas.drawRect(left + 2, top + 2, left + danmaku.paintWidth - 2, top + danmaku.paintHeight - 2, paint); 180 | } 181 | 182 | @Override 183 | public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) { 184 | // 禁用描边绘制 185 | } 186 | } 187 | 188 | private BaseDanmakuParser createParser(InputStream stream) { 189 | 190 | if (stream == null) { 191 | return new BaseDanmakuParser() { 192 | 193 | @Override 194 | protected Danmakus parse() { 195 | return new Danmakus(); 196 | } 197 | }; 198 | } 199 | 200 | ILoader loader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI); 201 | 202 | try { 203 | loader.load(stream); 204 | } catch (IllegalDataException e) { 205 | e.printStackTrace(); 206 | } 207 | BaseDanmakuParser parser = new BiliDanmukuParser(); 208 | IDataSource dataSource = loader.getDataSource(); 209 | parser.load(dataSource); 210 | return parser; 211 | 212 | } 213 | 214 | Timer timer = new Timer(); 215 | 216 | class AsyncAddTask extends TimerTask { 217 | 218 | @Override 219 | public void run() { 220 | for (int i = 0; i < 20; i++) { 221 | addDanmaku(true); 222 | SystemClock.sleep(20); 223 | } 224 | } 225 | }; 226 | 227 | /**发送一条弹幕*/ 228 | public void addDanmaku(boolean islive,String content) { 229 | BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); 230 | if (danmaku == null || mDanmakuView == null) { 231 | return; 232 | } 233 | danmaku.text = "我:"+content + "~来自iPhone X"; 234 | danmaku.padding = 5; 235 | //可能会被各种过滤器过滤并隐藏显示 236 | danmaku.priority = 1; 237 | danmaku.isLive = islive; 238 | //设置发送后多久显示 239 | danmaku.setTime(mDanmakuView.getCurrentTime() + 1500); 240 | danmaku.textSize = 50f * (mParser.getDisplayer().getDensity() - 0.6f); 241 | danmaku.textColor = Color.RED; 242 | danmaku.textShadowColor = Color.WHITE; 243 | // danmaku.underlineColor = Color.GREEN; 244 | danmaku.borderColor = Color.GREEN; 245 | danmaku.setTimer(new DanmakuTimer(3000)); 246 | mDanmakuView.addDanmaku(danmaku); 247 | 248 | } 249 | 250 | /**发送其它类型的弹幕*/ 251 | private void addDanmaKuShowTextAndImage(boolean islive) { 252 | BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); 253 | Drawable drawable = content.getResources().getDrawable(R.mipmap.ic_launcher); 254 | drawable.setBounds(0, 0, 100, 100); 255 | SpannableStringBuilder spannable = createSpannable(drawable); 256 | danmaku.text = spannable; 257 | danmaku.padding = 5; 258 | danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕 259 | danmaku.isLive = islive; 260 | danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); 261 | danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); 262 | danmaku.textColor = Color.RED; 263 | danmaku.textShadowColor = 0; // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低 264 | danmaku.underlineColor = Color.GREEN; 265 | mDanmakuView.addDanmaku(danmaku); 266 | } 267 | 268 | private SpannableStringBuilder createSpannable(Drawable drawable) { 269 | String text = "bitmap"; 270 | SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text); 271 | ImageSpan span = new ImageSpan(drawable);//ImageSpan.ALIGN_BOTTOM); 272 | spannableStringBuilder.setSpan(span, 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 273 | spannableStringBuilder.append("图文混排"); 274 | spannableStringBuilder.setSpan(new BackgroundColorSpan(Color.parseColor("#8A2233B1")), 0, spannableStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); 275 | return spannableStringBuilder; 276 | } 277 | 278 | 279 | private BaseCacheStuffer.Proxy mCacheStufferAdapter = new BaseCacheStuffer.Proxy() { 280 | 281 | private Drawable mDrawable; 282 | 283 | @Override 284 | public void prepareDrawing(final BaseDanmaku danmaku, boolean fromWorkerThread) { 285 | if (danmaku.text instanceof Spanned) { // 根据你的条件检查是否需要需要更新弹幕 286 | // FIXME 这里只是简单启个线程来加载远程url图片,请使用你自己的异步线程池,最好加上你的缓存池 287 | new Thread() { 288 | 289 | @Override 290 | public void run() { 291 | String url = "http://www.bilibili.com/favicon.ico"; 292 | InputStream inputStream = null; 293 | Drawable drawable = mDrawable; 294 | if(drawable == null) { 295 | try { 296 | URLConnection urlConnection = new URL(url).openConnection(); 297 | inputStream = urlConnection.getInputStream(); 298 | drawable = BitmapDrawable.createFromStream(inputStream, "bitmap"); 299 | mDrawable = drawable; 300 | } catch (MalformedURLException e) { 301 | e.printStackTrace(); 302 | } catch (IOException e) { 303 | e.printStackTrace(); 304 | } finally { 305 | IOUtils.closeQuietly(inputStream); 306 | } 307 | } 308 | if (drawable != null) { 309 | drawable.setBounds(0, 0, 100, 100); 310 | SpannableStringBuilder spannable = createSpannable(drawable); 311 | danmaku.text = spannable; 312 | if(mDanmakuView != null) { 313 | mDanmakuView.invalidateDanmaku(danmaku, false); 314 | } 315 | return; 316 | } 317 | } 318 | }.start(); 319 | } 320 | } 321 | 322 | @Override 323 | public void releaseResource(BaseDanmaku danmaku) { 324 | // TODO 重要:清理含有ImageSpan的text中的一些占用内存的资源 例如drawable 325 | } 326 | }; 327 | } 328 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/TecentVideoTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo; 2 | 3 | import android.support.multidex.MultiDexApplication; 4 | 5 | /** 6 | * Created by zjp on 2018/5/29 17:20. 7 | */ 8 | 9 | public class TecentVideoTestApplication extends MultiDexApplication { 10 | 11 | private static TecentVideoTestApplication instance; 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | instance = this; 17 | } 18 | 19 | public static TecentVideoTestApplication getApplication() { 20 | return instance; 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/beautysettings/TCHorizontalScrollView.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo.beautysettings; 2 | 3 | import android.content.Context; 4 | import android.database.DataSetObserver; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Adapter; 10 | 11 | 12 | public class TCHorizontalScrollView extends android.widget.HorizontalScrollView { 13 | 14 | public TCHorizontalScrollView(Context context) { 15 | super(context); 16 | initialize(); 17 | } 18 | 19 | public TCHorizontalScrollView(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | initialize(); 22 | } 23 | 24 | public TCHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 25 | super(context, attrs, defStyleAttr); 26 | initialize(); 27 | } 28 | 29 | private DataSetObserver observer; 30 | public void setAdapter(Adapter adapter) { 31 | if (this.adapter != null) { 32 | this.adapter.unregisterDataSetObserver(observer); 33 | } 34 | this.adapter = adapter; 35 | adapter.registerDataSetObserver(observer); 36 | updateAdapter(); 37 | } 38 | 39 | private void updateAdapter() { 40 | ViewGroup group = (ViewGroup)getChildAt(0); 41 | group.removeAllViews(); 42 | 43 | for (int i = 0; i= Build.VERSION_CODES.JELLY_BEAN_MR1) { 71 | getChildAt(0).setPaddingRelative(side, 0, side, 0); 72 | } else { 73 | getChildAt(0).setPadding(side,0,side,0); 74 | } 75 | 76 | } 77 | 78 | public void setClicked(int position) { 79 | ((ViewGroup)getChildAt(0)).getChildAt(position).performClick(); 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/ui/LiveActivity.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo.ui; 2 | 3 | import android.content.Intent; 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.support.v7.widget.SwitchCompat; 10 | import android.text.TextUtils; 11 | import android.util.Log; 12 | import android.view.Display; 13 | import android.view.View; 14 | import android.view.WindowManager; 15 | import android.widget.Button; 16 | import android.widget.CompoundButton; 17 | import android.widget.FrameLayout; 18 | import android.widget.ImageView; 19 | import android.widget.ListView; 20 | import android.widget.ProgressBar; 21 | import android.widget.TextView; 22 | import android.widget.Toast; 23 | 24 | import com.opensource.svgaplayer.SVGADrawable; 25 | import com.opensource.svgaplayer.SVGAImageView; 26 | import com.opensource.svgaplayer.SVGAParser; 27 | import com.opensource.svgaplayer.SVGAVideoEntity; 28 | import com.tencent.rtmp.ITXLivePlayListener; 29 | import com.tencent.rtmp.TXLiveConstants; 30 | import com.tencent.rtmp.TXLivePlayConfig; 31 | import com.tencent.rtmp.TXLivePlayer; 32 | import com.tencent.rtmp.ui.TXCloudVideoView; 33 | import com.tencent.ugc.TXRecordCommon; 34 | import com.zjp.tencentvideo.DanmakuController; 35 | import com.zjp.tencentvideo.R; 36 | import com.zjp.tencentvideo.utils.TCFrequeControl; 37 | import com.zjp.tencentvideo.view.TCInputTextMsgDialog; 38 | import com.zjp.tencentvideo.view.like.TCHeartLayout; 39 | 40 | import java.io.File; 41 | import java.net.MalformedURLException; 42 | import java.net.URL; 43 | 44 | import master.flame.danmaku.ui.widget.DanmakuView; 45 | 46 | /** 47 | * Created by zjp on 2018/5/30 13:53. 48 | */ 49 | 50 | public class LiveActivity extends AppCompatActivity implements TCInputTextMsgDialog.OnTextSendListener, View.OnClickListener, ITXLivePlayListener { 51 | 52 | private TXCloudVideoView mTxCloudVideoView; 53 | private DanmakuView danmakuView; 54 | private SwitchCompat mSwitchBt; 55 | private RecyclerView mRv_user_avatar; 56 | private ImageView mBtn_message_input, mbtn_record, mbtn_like, mclose_record, mrecord, mretry_record, mPlayBtn, mbtn_linkmic, mBtnRenderRotation; 57 | private Button mbtn_back; 58 | private ListView mim_msg_listview; 59 | private ProgressBar mrecord_progress; 60 | private TCHeartLayout mheart_layout; 61 | private DanmakuView mDanmakuView; 62 | private TCHeartLayout mHeartLayout; 63 | private TextView mtv_animation; 64 | private SVGAImageView mimageView; 65 | 66 | private TCFrequeControl mLikeFrequeControl; 67 | 68 | private int mCurrentRenderMode; 69 | private int mCurrentRenderRotation; 70 | 71 | private boolean flag; 72 | private boolean isStopAnimation; 73 | 74 | private TXLivePlayConfig mPlayConfig; 75 | private long mStartPlayTS = 0; 76 | 77 | /** 78 | * inputDialog 79 | */ 80 | TCInputTextMsgDialog mInputTextMsgDialog; 81 | 82 | /** 83 | * 弹幕 84 | */ 85 | DanmakuController mDanmakuController; 86 | 87 | private TXLivePlayer mLivePlayer; 88 | 89 | @Override 90 | protected void onCreate(@Nullable Bundle savedInstanceState) { 91 | super.onCreate(savedInstanceState); 92 | setContentView(R.layout.activity_live); 93 | 94 | initView(); 95 | initData(); 96 | initListener(); 97 | } 98 | 99 | private void initView() { 100 | 101 | mCurrentRenderMode = TXLiveConstants.RENDER_MODE_ADJUST_RESOLUTION; 102 | mCurrentRenderRotation = TXLiveConstants.RENDER_ROTATION_PORTRAIT; 103 | 104 | mTxCloudVideoView = findViewById(R.id.video_view); 105 | danmakuView = findViewById(R.id.danmaku); 106 | mSwitchBt = findViewById(R.id.switch_bt); 107 | mRv_user_avatar = findViewById(R.id.rv_user_avatar); 108 | mBtn_message_input = findViewById(R.id.btn_message_input); 109 | mtv_animation = findViewById(R.id.tv_animation); 110 | mbtn_record = findViewById(R.id.btn_record); 111 | mbtn_like = findViewById(R.id.btn_like); 112 | mbtn_back = findViewById(R.id.btn_back); 113 | mim_msg_listview = findViewById(R.id.im_msg_listview); 114 | mrecord_progress = findViewById(R.id.record_progress); 115 | mclose_record = findViewById(R.id.close_record); 116 | mrecord = findViewById(R.id.record); 117 | mretry_record = findViewById(R.id.retry_record); 118 | mheart_layout = findViewById(R.id.heart_layout); 119 | mDanmakuView = findViewById(R.id.danmakuView); 120 | mPlayBtn = findViewById(R.id.play_btn); 121 | mbtn_linkmic = findViewById(R.id.btn_linkmic); 122 | mHeartLayout = findViewById(R.id.heart_layout); 123 | mBtnRenderRotation = findViewById(R.id.btnOrientation); 124 | mimageView = findViewById(R.id.imageView); 125 | } 126 | 127 | private void initData() { 128 | 129 | mbtn_record.setVisibility(View.GONE); 130 | 131 | 132 | mInputTextMsgDialog = new TCInputTextMsgDialog(this, R.style.InputDialog); 133 | mInputTextMsgDialog.setmOnTextSendListener(this); 134 | initRtmpPlayer(); 135 | initDanmaKu(); 136 | } 137 | 138 | private void initListener() { 139 | mBtn_message_input.setOnClickListener(this); 140 | mbtn_linkmic.setOnClickListener(this); 141 | mbtn_like.setOnClickListener(this); 142 | mBtnRenderRotation.setOnClickListener(this); 143 | mtv_animation.setOnClickListener(this); 144 | } 145 | 146 | /** 147 | * 初始化播放 148 | */ 149 | private void initRtmpPlayer() { 150 | 151 | mTxCloudVideoView.setLogMargin(12, 12, 110, 60); 152 | mTxCloudVideoView.showLog(false); 153 | //创建 player 对象 154 | mLivePlayer = new TXLivePlayer(this); 155 | //关键 player 对象与界面 view 156 | mLivePlayer.setPlayerView(mTxCloudVideoView); 157 | 158 | 159 | mPlayConfig = new TXLivePlayConfig(); 160 | //自动模式 161 | mPlayConfig.setAutoAdjustCacheTime(true); 162 | mPlayConfig.setCacheTime(5.0f); 163 | mPlayConfig.setMaxAutoAdjustCacheTime(5.0f); 164 | mPlayConfig.setMinAutoAdjustCacheTime(1.0f); 165 | 166 | // 设置播放器缓存时间. 167 | mPlayConfig.setMaxCacheItems(2); 168 | //设置播放器重连次数. 169 | mPlayConfig.setConnectRetryCount(5); 170 | //设置播放器重连间隔. 171 | mPlayConfig.setConnectRetryInterval(3); 172 | //开启就近选路 非腾讯云不用 173 | mPlayConfig.setEnableNearestIP(false); 174 | //设置自动调整时播放器最大缓存时间. 175 | mPlayConfig.setMaxAutoAdjustCacheTime(5.0f); 176 | 177 | //设置RTMP传输通道的类型 178 | // mPlayConfig.setRtmpChannelType(TXLivePlayer.PLAY_TYPE_LIVE_RTMP); 179 | mLivePlayer.setConfig(mPlayConfig); 180 | mLivePlayer.setPlayListener(this); 181 | //将图像等比例铺满整个屏幕,多余部分裁剪掉,此模式下画面不会留黑边,但可能因为部分区域被裁剪而显示不全。 182 | mLivePlayer.setRenderMode(TXLiveConstants.RENDER_MODE_FULL_FILL_SCREEN); 183 | //正常播放 184 | mLivePlayer.setRenderRotation(TXLiveConstants.RENDER_ROTATION_PORTRAIT); 185 | //硬件加速 186 | mLivePlayer.enableHardwareDecode(true); 187 | 188 | mTxCloudVideoView.setVisibility(View.VISIBLE); 189 | 190 | String url = "rtmp://24649.liveplay.myqcloud.com/live/24649_743e7a0df4"; 191 | mLivePlayer.startPlay(url, TXLivePlayer.PLAY_TYPE_LIVE_RTMP); 192 | 193 | mStartPlayTS = System.currentTimeMillis(); 194 | } 195 | 196 | /** 197 | * 初始化弹幕 198 | */ 199 | private void initDanmaKu() { 200 | mDanmakuController = new DanmakuController(mDanmakuView, this); 201 | mDanmakuController.initDanmaKu(); 202 | //默认不显示 203 | mDanmakuView.hide(); 204 | //总开关 205 | mSwitchBt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 206 | @Override 207 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 208 | if (isChecked) { 209 | mDanmakuView.show(); 210 | } else { 211 | mDanmakuView.hide(); 212 | } 213 | } 214 | }); 215 | } 216 | 217 | /** 218 | * 发送弹幕后的回调 219 | */ 220 | @Override 221 | public void onTextSend(String msg, boolean tanmuOpen) { 222 | if (msg.length() == 0) { 223 | 224 | return; 225 | } 226 | //关联弹幕总开关 227 | if (tanmuOpen) { 228 | mDanmakuView.show(); 229 | mSwitchBt.setChecked(true); 230 | } 231 | //消息显示 232 | mDanmakuController.addDanmaku(tanmuOpen, msg); 233 | } 234 | 235 | /** 236 | * 发送消息弹出框 237 | */ 238 | private void showInputMsgDialog() { 239 | WindowManager windowManager = getWindowManager(); 240 | Display display = windowManager.getDefaultDisplay(); 241 | WindowManager.LayoutParams lp = mInputTextMsgDialog.getWindow().getAttributes(); 242 | lp.width = (display.getWidth()); 243 | mInputTextMsgDialog.getWindow().setAttributes(lp); 244 | mInputTextMsgDialog.setCancelable(true); 245 | mInputTextMsgDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 246 | mInputTextMsgDialog.show(); 247 | } 248 | 249 | @Override 250 | public void onConfigurationChanged(Configuration newConfig) { 251 | super.onConfigurationChanged(newConfig); 252 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 253 | mDanmakuView.getConfig().setDanmakuMargin(20); 254 | } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { 255 | mDanmakuView.getConfig().setDanmakuMargin(40); 256 | } 257 | } 258 | 259 | @Override 260 | public void onClick(View v) { 261 | switch (v.getId()) { 262 | case R.id.btn_message_input: 263 | showInputMsgDialog(); 264 | break; 265 | case R.id.btn_linkmic: 266 | flag = !flag; 267 | if (flag) { 268 | stopPlay(); 269 | } else { 270 | startPlay(); 271 | } 272 | break; 273 | 274 | case R.id.btn_like: 275 | /**点赞*/ 276 | if (mHeartLayout != null) { 277 | mHeartLayout.setVisibility(View.VISIBLE); 278 | mHeartLayout.addFavor(); 279 | } 280 | //点赞发送请求限制 281 | if (mLikeFrequeControl == null) { 282 | mLikeFrequeControl = new TCFrequeControl(); 283 | mLikeFrequeControl.init(2, 1); 284 | } 285 | break; 286 | 287 | case R.id.btnOrientation: 288 | if (mLivePlayer == null) { 289 | return; 290 | } 291 | 292 | if (mCurrentRenderRotation == TXLiveConstants.RENDER_ROTATION_PORTRAIT) { 293 | mBtnRenderRotation.setBackgroundResource(R.drawable.portrait); 294 | mCurrentRenderRotation = TXLiveConstants.RENDER_ROTATION_LANDSCAPE; 295 | } else if (mCurrentRenderRotation == TXLiveConstants.RENDER_ROTATION_LANDSCAPE) { 296 | mBtnRenderRotation.setBackgroundResource(R.drawable.landscape); 297 | mCurrentRenderRotation = TXLiveConstants.RENDER_ROTATION_PORTRAIT; 298 | } 299 | 300 | mLivePlayer.setRenderRotation(mCurrentRenderRotation); 301 | 302 | break; 303 | 304 | case R.id.tv_animation: 305 | isStopAnimation = !isStopAnimation; 306 | if (isStopAnimation) { 307 | mimageView.setVisibility(View.VISIBLE); 308 | SVGAParser parser = new SVGAParser(this); 309 | try { 310 | parser.parse(new URL("https://github.com/yyued/SVGA-Samples/blob/master/kingset.svga?raw=true"), new SVGAParser.ParseCompletion() { 311 | @Override 312 | public void onComplete(SVGAVideoEntity mSVGAVideoEntity) { 313 | SVGADrawable drawable = new SVGADrawable(mSVGAVideoEntity); 314 | mimageView.setImageDrawable(drawable); 315 | mimageView.startAnimation(); 316 | } 317 | 318 | @Override 319 | public void onError() { 320 | Toast.makeText(LiveActivity.this, "parse error!", Toast.LENGTH_SHORT).show(); 321 | } 322 | }); 323 | } catch (Exception e) { 324 | System.out.print(true); 325 | } 326 | } else { 327 | mimageView.stopAnimation(); 328 | mimageView.setVisibility(View.GONE); 329 | } 330 | break; 331 | } 332 | } 333 | 334 | private void stopPlay() { 335 | if (mLivePlayer != null) { 336 | mLivePlayer.pause(); // 暂停 337 | } 338 | mbtn_linkmic.setBackgroundResource(R.drawable.play_start); 339 | } 340 | 341 | private void startPlay() { 342 | if (mLivePlayer != null) { 343 | mLivePlayer.resume(); // 继续 344 | } 345 | mbtn_linkmic.setBackgroundResource(R.drawable.play_pause); 346 | } 347 | 348 | @Override 349 | protected void onPause() { 350 | super.onPause(); 351 | if (mDanmakuView != null && mDanmakuView.isPrepared()) { 352 | mDanmakuView.pause(); 353 | } 354 | } 355 | 356 | @Override 357 | protected void onResume() { 358 | super.onResume(); 359 | if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) { 360 | mDanmakuView.resume(); 361 | } 362 | } 363 | 364 | @Override 365 | protected void onDestroy() { 366 | super.onDestroy(); 367 | if (mDanmakuView != null) { 368 | // dont forget release! 369 | mDanmakuView.release(); 370 | mDanmakuView = null; 371 | } 372 | if (mTxCloudVideoView != null) { 373 | mLivePlayer.stopPlay(true); // true 代表清除最后一帧画面 374 | mTxCloudVideoView.onDestroy(); 375 | 376 | } 377 | } 378 | 379 | @Override 380 | public void onBackPressed() { 381 | super.onBackPressed(); 382 | if (mDanmakuView != null) { 383 | // dont forget release! 384 | mDanmakuView.release(); 385 | mDanmakuView = null; 386 | } 387 | } 388 | 389 | @Override 390 | public void onPlayEvent(int event, Bundle bundle) { 391 | String playEventLog = "receive event: " + event + ", " + bundle.getString(TXLiveConstants.EVT_DESCRIPTION); 392 | Log.d("zjp", "playEventLog=" + playEventLog); 393 | 394 | if (event == TXLiveConstants.PLAY_EVT_PLAY_BEGIN) { 395 | Log.d("zjp", "PlayFirstRender,cost=" + (System.currentTimeMillis() - mStartPlayTS)); 396 | } else if (event == TXLiveConstants.PLAY_ERR_NET_DISCONNECT || event == TXLiveConstants.PLAY_EVT_PLAY_END) { 397 | stopPlay(); 398 | } else if (event == TXLiveConstants.PLAY_EVT_PLAY_LOADING) { 399 | // startLoadingAnimation(); 400 | } else if (event == TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME) { 401 | // stopLoadingAnimation(); 402 | } else if (event == TXLiveConstants.PLAY_EVT_CHANGE_RESOLUTION) { 403 | // streamRecord(false); 404 | } else if (event == TXLiveConstants.PLAY_EVT_CHANGE_ROTATION) { 405 | return; 406 | } 407 | 408 | if (event < 0) { 409 | Toast.makeText(getApplicationContext(), bundle.getString(TXLiveConstants.EVT_DESCRIPTION), Toast.LENGTH_SHORT).show(); 410 | } 411 | } 412 | 413 | @Override 414 | public void onNetStatus(Bundle bundle) { 415 | String str = getNetStatusString(bundle); 416 | Log.d("zjp", "str=" + str); 417 | Log.d("zjp", "Current status, CPU:" + bundle.getString(TXLiveConstants.NET_STATUS_CPU_USAGE) + 418 | ", RES:" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_WIDTH) + "*" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_HEIGHT) + 419 | ", SPD:" + bundle.getInt(TXLiveConstants.NET_STATUS_NET_SPEED) + "Kbps" + 420 | ", FPS:" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_FPS) + 421 | ", ARA:" + bundle.getInt(TXLiveConstants.NET_STATUS_AUDIO_BITRATE) + "Kbps" + 422 | ", VRA:" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_BITRATE) + "Kbps"); 423 | } 424 | 425 | 426 | //公用打印辅助函数 427 | protected String getNetStatusString(Bundle status) { 428 | String str = String.format("%-14s %-14s %-12s\n%-8s %-8s %-8s %-8s\n%-14s %-14s\n%-14s %-14s", 429 | "CPU:" + status.getString(TXLiveConstants.NET_STATUS_CPU_USAGE), 430 | "RES:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_WIDTH) + "*" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_HEIGHT), 431 | "SPD:" + status.getInt(TXLiveConstants.NET_STATUS_NET_SPEED) + "Kbps", 432 | "JIT:" + status.getInt(TXLiveConstants.NET_STATUS_NET_JITTER), 433 | "FPS:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_FPS), 434 | "GOP:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_GOP) + "s", 435 | "ARA:" + status.getInt(TXLiveConstants.NET_STATUS_AUDIO_BITRATE) + "Kbps", 436 | "QUE:" + status.getInt(TXLiveConstants.NET_STATUS_CODEC_CACHE) 437 | + "|" + status.getInt(TXLiveConstants.NET_STATUS_CACHE_SIZE) 438 | + "," + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_CACHE_SIZE) 439 | + "," + status.getInt(TXLiveConstants.NET_STATUS_V_DEC_CACHE_SIZE) 440 | + "|" + status.getInt(TXLiveConstants.NET_STATUS_AV_RECV_INTERVAL) 441 | + "," + status.getInt(TXLiveConstants.NET_STATUS_AV_PLAY_INTERVAL) 442 | + "," + String.format("%.1f", status.getFloat(TXLiveConstants.NET_STATUS_AUDIO_PLAY_SPEED)).toString(), 443 | "VRA:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_BITRATE) + "Kbps", 444 | "SVR:" + status.getString(TXLiveConstants.NET_STATUS_SERVER_IP), 445 | "AUDIO:" + status.getString(TXLiveConstants.NET_STATUS_AUDIO_INFO)); 446 | return str; 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo.ui; 2 | 3 | import android.Manifest; 4 | import android.app.ListActivity; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.support.annotation.NonNull; 9 | import android.support.v4.app.ActivityCompat; 10 | import android.os.Bundle; 11 | import android.view.View; 12 | import android.widget.ArrayAdapter; 13 | import android.widget.ListView; 14 | import android.widget.Toast; 15 | 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public class MainActivity extends ListActivity { 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | // setContentView(R.layout.activity_main); 26 | 27 | setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mTitles)); 28 | checkPermission(); 29 | } 30 | 31 | private String[] mTitles = new String[]{ 32 | "推流", 33 | "直播" 34 | }; 35 | 36 | private Class[] mActivities = new Class[]{ 37 | RTMPActivity.class, 38 | LiveActivity.class, 39 | }; 40 | 41 | @Override 42 | protected void onListItemClick(ListView l, View v, int position, long id) { 43 | super.onListItemClick(l, v, position, id); 44 | if (position == 0) { 45 | startActivity(new Intent(this, mActivities[0])); 46 | 47 | } else if (position == 1) { 48 | if (checkPermissionPush()) { 49 | startActivity(new Intent(this, mActivities[1])); 50 | } else { 51 | Toast.makeText(this, "权限不足", Toast.LENGTH_SHORT).show(); 52 | } 53 | } 54 | } 55 | 56 | private boolean checkPermissionPush() { 57 | if (Build.VERSION.SDK_INT >= 23) { 58 | List permissions = new ArrayList<>(); 59 | if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { 60 | permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); 61 | } 62 | if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)) { 63 | permissions.add(Manifest.permission.CAMERA); 64 | } 65 | if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)) { 66 | permissions.add(Manifest.permission.RECORD_AUDIO); 67 | } 68 | if (permissions.size() != 0) { 69 | ActivityCompat.requestPermissions(this, 70 | permissions.toArray(new String[0]), 71 | 1); 72 | return false; 73 | } 74 | } 75 | 76 | return true; 77 | } 78 | 79 | private boolean checkPermission() { 80 | if (Build.VERSION.SDK_INT >= 26) { 81 | List permissions = new ArrayList<>(); 82 | if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW)) { 83 | permissions.add(Manifest.permission.SYSTEM_ALERT_WINDOW); 84 | } 85 | if (permissions.size() != 0) { 86 | ActivityCompat.requestPermissions(this, permissions.toArray(new String[0]), 2); 87 | return false; 88 | } 89 | } 90 | return true; 91 | } 92 | 93 | 94 | @Override 95 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 96 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 97 | switch (requestCode) { 98 | case 1: 99 | for (int ret : grantResults) { 100 | if (ret != PackageManager.PERMISSION_GRANTED) { 101 | return; 102 | } 103 | } 104 | break; 105 | default: 106 | break; 107 | } 108 | } 109 | 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/ui/RTMPActivity.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo.ui; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.provider.Settings; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.util.Log; 11 | import android.view.Surface; 12 | import android.view.View; 13 | import android.widget.Button; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import com.tencent.rtmp.ITXLivePushListener; 18 | import com.tencent.rtmp.TXLiveConstants; 19 | import com.tencent.rtmp.TXLivePushConfig; 20 | import com.tencent.rtmp.TXLivePusher; 21 | import com.tencent.rtmp.ui.TXCloudVideoView; 22 | import com.zjp.tencentvideo.R; 23 | import com.zjp.tencentvideo.beautysettings.BeautyDialogFragment; 24 | import com.zjp.tencentvideo.utils.BitmapUtils; 25 | import com.zjp.tencentvideo.utils.TCUtils; 26 | 27 | /** 28 | * Created by zjp on 2018/5/29 14:48. 29 | * 推流 30 | */ 31 | 32 | public class RTMPActivity extends AppCompatActivity implements ITXLivePushListener, BeautyDialogFragment.OnBeautyParamsChangeListener, View.OnClickListener { 33 | 34 | private TXCloudVideoView mTxCloudVideoView; 35 | private TXLivePushConfig mLivePushConfig; 36 | private TXLivePusher mLivePusher; 37 | private Button mBtnFaceBeauty, mBtnMsgInput, mBtnCameraChange, mBtnClose, mBtnLinkMic; 38 | private View mRootView; 39 | private TextView mNetBusyTips; 40 | 41 | // private l 42 | 43 | 44 | private static final int VIDEO_SRC_CAMERA = 0; 45 | private static final int VIDEO_SRC_SCREEN = 1; 46 | private int mVideoSrc = VIDEO_SRC_CAMERA; 47 | 48 | boolean mVideoPublish; 49 | private int mNetBusyCount = 0; 50 | 51 | private Handler mMainHandler; 52 | 53 | //美颜管理 54 | BeautyDialogFragment mBeautyDialogFragment; 55 | BeautyDialogFragment.BeautyParams mBeautyParams; 56 | 57 | 58 | @Override 59 | protected void onCreate(@Nullable Bundle savedInstanceState) { 60 | super.onCreate(savedInstanceState); 61 | setContentView(R.layout.activity_rtmp); 62 | 63 | // String version = TXLiveBase.getSDKVersionStr(); 64 | // Log.d("zjp", "version=" + version); 65 | 66 | initView(); 67 | initData(); 68 | initListener(); 69 | } 70 | 71 | private void initView() { 72 | mMainHandler = new Handler(Looper.getMainLooper()); 73 | mTxCloudVideoView = findViewById(R.id.txcloutvideo); 74 | mBtnFaceBeauty = findViewById(R.id.btnFaceBeauty); 75 | mRootView = findViewById(R.id.rootview); 76 | mBtnMsgInput = findViewById(R.id.btn_message_input); 77 | mBtnCameraChange = findViewById(R.id.btnCameraChange); 78 | mBtnClose = findViewById(R.id.btn_close); 79 | mNetBusyTips = findViewById(R.id.netbusy_tv); 80 | mBtnLinkMic = findViewById(R.id.rtmproom_linkmic_btn); 81 | 82 | } 83 | 84 | private void initData() { 85 | 86 | mTxCloudVideoView.setLogMargin(12, 12, 110, 60); 87 | mTxCloudVideoView.showLog(false); 88 | 89 | //推流 90 | mLivePusher = new TXLivePusher(this); 91 | 92 | mLivePushConfig = new TXLivePushConfig(); 93 | mLivePusher.setConfig(mLivePushConfig); 94 | 95 | mVideoPublish = startPusherRtmp(); 96 | 97 | mBeautyDialogFragment = new BeautyDialogFragment(); 98 | mBeautyParams = new BeautyDialogFragment.BeautyParams(); 99 | //美颜选择监听 100 | mBeautyDialogFragment.setBeautyParamsListner(mBeautyParams, this); 101 | } 102 | 103 | private void initListener() { 104 | mBtnMsgInput.setOnClickListener(this); 105 | mBtnFaceBeauty.setOnClickListener(this); 106 | mBtnCameraChange.setOnClickListener(this); 107 | mBtnClose.setOnClickListener(this); 108 | mBtnLinkMic.setOnClickListener(this); 109 | } 110 | 111 | /** 112 | * 启动推流 113 | */ 114 | private boolean startPusherRtmp() { 115 | 116 | //开启相机 117 | if (mVideoSrc != VIDEO_SRC_SCREEN) { 118 | mTxCloudVideoView.setVisibility(View.VISIBLE); 119 | } 120 | 121 | Bitmap bitmap = BitmapUtils.decodeResource(getResources(), R.mipmap.pause_publish); 122 | //水印 123 | mLivePushConfig.setWatermark(bitmap, 0.02f, 0.05f, 0.2f); 124 | //参数分别是水印图片的 Bitmap、水印位置的 X 轴坐标,水印位置的 Y 轴坐标,水印宽度。后面三个参数取值范围是[0, 1],后面两个参数分别是水印位置的X轴坐标和 Y 轴坐标 125 | 126 | int customModeType = 0; 127 | 128 | // if (isActivityCanRotation()) { 129 | // onActivityRotation(); 130 | // } 131 | 132 | mLivePushConfig.setCustomModeType(customModeType); 133 | mLivePusher.setPushListener(this); 134 | mLivePushConfig.setPauseImg(300, 5); 135 | mLivePushConfig.setPauseImg(bitmap); 136 | mLivePushConfig.setPauseFlag(TXLiveConstants.PAUSE_FLAG_PAUSE_VIDEO | TXLiveConstants.PAUSE_FLAG_PAUSE_AUDIO); 137 | if (mVideoSrc != VIDEO_SRC_SCREEN) { 138 | mLivePushConfig.setFrontCamera(true); 139 | mLivePushConfig.setBeautyFilter(5, 3, 2); 140 | mLivePusher.setConfig(mLivePushConfig); 141 | mLivePusher.startCameraPreview(mTxCloudVideoView); // //是将界面元素和 Pusher 对象关联起来,从而能够将手机摄像头采集到的画面渲染到屏幕上。 142 | } else { 143 | mLivePusher.setConfig(mLivePushConfig); 144 | mLivePusher.startScreenCapture(); 145 | } 146 | 147 | String rtmpUrl = "rtmp://24649.livepush.myqcloud.com/live/24649_743e7a0df4?bizid=24649&txSecret=980911a90e1d3dbcde9ee86746bc0ee6&txTime=5B1561FF"; 148 | mLivePusher.startPusher(rtmpUrl); //告诉 SDK 音视频流要推到哪个推流URL上去 149 | return true; 150 | } 151 | 152 | 153 | /** 154 | * 开始直播的回调 155 | */ 156 | @Override 157 | public void onPushEvent(int event, Bundle bundle) { 158 | String msg = bundle.getString(TXLiveConstants.EVT_DESCRIPTION); 159 | Log.d("zjp", "receive event: " + event + ", " + msg); 160 | 161 | //错误还是要明确的报一下 162 | if (event < 0) { 163 | Toast.makeText(getApplicationContext(), bundle.getString(TXLiveConstants.EVT_DESCRIPTION), Toast.LENGTH_SHORT).show(); 164 | if (event == TXLiveConstants.PUSH_ERR_OPEN_CAMERA_FAIL || event == TXLiveConstants.PUSH_ERR_OPEN_MIC_FAIL) { 165 | stopPublishRtmp(); 166 | } 167 | } 168 | 169 | if (event == TXLiveConstants.PUSH_ERR_NET_DISCONNECT) { //网络断连,且经三次抢救无效,可以放弃治疗,更多重试请自行重启推流 170 | stopPublishRtmp(); 171 | } else if (event == TXLiveConstants.PUSH_WARNING_HW_ACCELERATION_FAIL) { //硬编码启动失败,采用软编码 172 | Toast.makeText(getApplicationContext(), bundle.getString(TXLiveConstants.EVT_DESCRIPTION), Toast.LENGTH_SHORT).show(); 173 | mLivePushConfig.setHardwareAcceleration(TXLiveConstants.ENCODE_VIDEO_SOFTWARE); 174 | // mBtnHWEncode.setBackgroundResource(R.drawable.quick2); 175 | mLivePusher.setConfig(mLivePushConfig); 176 | // mHWVideoEncode = false; 177 | } else if (event == TXLiveConstants.PUSH_ERR_SCREEN_CAPTURE_UNSURPORT) { 178 | stopPublishRtmp(); 179 | } else if (event == TXLiveConstants.PUSH_ERR_SCREEN_CAPTURE_START_FAILED) { 180 | stopPublishRtmp(); 181 | } else if (event == TXLiveConstants.PUSH_EVT_CHANGE_RESOLUTION) { //推流动态调整分辨率 182 | Log.d("zjp", "change resolution to " + bundle.getInt(TXLiveConstants.EVT_PARAM2) + ", bitrate to" + bundle.getInt(TXLiveConstants.EVT_PARAM1)); 183 | } else if (event == TXLiveConstants.PUSH_EVT_CHANGE_BITRATE) { //推流动态调整码率 184 | Log.d("zjp", "change bitrate to" + bundle.getInt(TXLiveConstants.EVT_PARAM1)); 185 | } else if (event == TXLiveConstants.PUSH_WARNING_NET_BUSY) { //当前网络已经非常糟糕 186 | ++mNetBusyCount; 187 | Log.d("zjp", "net busy. count=" + mNetBusyCount); 188 | showNetBusyTips(); 189 | } else if (event == TXLiveConstants.PUSH_EVT_START_VIDEO_ENCODER) { 190 | int encType = bundle.getInt(TXLiveConstants.EVT_PARAM1); 191 | // mHWVideoEncode = (encType == 1); 192 | // mBtnHWEncode.getBackground().setAlpha(mHWVideoEncode ? 255 : 100); 193 | } 194 | } 195 | 196 | @Override 197 | public void onNetStatus(Bundle bundle) { 198 | String str = getNetStatusString(bundle); 199 | Log.d("zjp", "Current status, CPU:" + bundle.getString(TXLiveConstants.NET_STATUS_CPU_USAGE) + 200 | ", RES:" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_WIDTH) + "*" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_HEIGHT) + 201 | ", SPD:" + bundle.getInt(TXLiveConstants.NET_STATUS_NET_SPEED) + "Kbps" + 202 | ", FPS:" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_FPS) + 203 | ", ARA:" + bundle.getInt(TXLiveConstants.NET_STATUS_AUDIO_BITRATE) + "Kbps" + 204 | ", VRA:" + bundle.getInt(TXLiveConstants.NET_STATUS_VIDEO_BITRATE) + "Kbps"); 205 | // if (mLivePusher != null){ 206 | // mLivePusher.onLogRecord("[net state]:\n"+str+"\n"); 207 | // } 208 | } 209 | 210 | /** 211 | * 关闭推流 212 | */ 213 | private void stopPublishRtmp() { 214 | mVideoPublish = false; 215 | mLivePusher.stopBGM(); 216 | mLivePusher.stopCameraPreview(true); 217 | mLivePusher.stopScreenCapture(); 218 | mLivePusher.setPushListener(null); 219 | mLivePusher.stopPusher(); 220 | mTxCloudVideoView.setVisibility(View.GONE); 221 | 222 | if (mLivePushConfig != null) { 223 | mLivePushConfig.setPauseImg(null); 224 | } 225 | } 226 | 227 | protected void onActivityRotation() { 228 | // 自动旋转打开,Activity随手机方向旋转之后,需要改变推流方向 229 | int mobileRotation = this.getWindowManager().getDefaultDisplay().getRotation(); 230 | int pushRotation = TXLiveConstants.VIDEO_ANGLE_HOME_DOWN; 231 | boolean screenCaptureLandscape = false; 232 | switch (mobileRotation) { 233 | case Surface.ROTATION_0: 234 | pushRotation = TXLiveConstants.VIDEO_ANGLE_HOME_DOWN; 235 | break; 236 | case Surface.ROTATION_180: 237 | pushRotation = TXLiveConstants.VIDEO_ANGLE_HOME_UP; 238 | break; 239 | case Surface.ROTATION_90: 240 | pushRotation = TXLiveConstants.VIDEO_ANGLE_HOME_RIGHT; 241 | screenCaptureLandscape = true; 242 | break; 243 | case Surface.ROTATION_270: 244 | pushRotation = TXLiveConstants.VIDEO_ANGLE_HOME_LEFT; 245 | screenCaptureLandscape = true; 246 | break; 247 | default: 248 | break; 249 | } 250 | mLivePusher.setRenderRotation(0); //因为activity也旋转了,本地渲染相对正方向的角度为0。 251 | mLivePushConfig.setHomeOrientation(pushRotation); 252 | if (mLivePusher.isPushing()) { 253 | if (VIDEO_SRC_CAMERA == mVideoSrc) { 254 | mLivePusher.setConfig(mLivePushConfig); 255 | mLivePusher.stopCameraPreview(true); 256 | mLivePusher.startCameraPreview(mTxCloudVideoView); 257 | } else if (VIDEO_SRC_SCREEN == mVideoSrc) { 258 | 259 | mLivePusher.setConfig(mLivePushConfig); 260 | mLivePusher.stopScreenCapture(); 261 | mLivePusher.startScreenCapture(); 262 | } 263 | } 264 | } 265 | 266 | private void showNetBusyTips() { 267 | if (mNetBusyTips.isShown()) { 268 | return; 269 | } 270 | mNetBusyTips.setVisibility(View.VISIBLE); 271 | mMainHandler.postDelayed(new Runnable() { 272 | @Override 273 | public void run() { 274 | mNetBusyTips.setVisibility(View.GONE); 275 | } 276 | }, 5000); 277 | } 278 | 279 | /** 280 | * 判断Activity是否可旋转。只有在满足以下条件的时候,Activity才是可根据重力感应自动旋转的。 281 | * 系统“自动旋转”设置项打开; 282 | * 283 | * @return false---Activity可根据重力感应自动旋转 284 | */ 285 | protected boolean isActivityCanRotation() { 286 | // 判断自动旋转是否打开 287 | int flag = Settings.System.getInt(this.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0); 288 | if (flag == 0) { 289 | return false; 290 | } 291 | return true; 292 | } 293 | 294 | //公用打印辅助函数 295 | protected String getNetStatusString(Bundle status) { 296 | String str = String.format("%-14s %-14s %-12s\n%-8s %-8s %-8s %-8s\n%-14s %-14s %-12s\n%-14s %-14s", 297 | "CPU:" + status.getString(TXLiveConstants.NET_STATUS_CPU_USAGE), 298 | "RES:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_WIDTH) + "*" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_HEIGHT), 299 | "SPD:" + status.getInt(TXLiveConstants.NET_STATUS_NET_SPEED) + "Kbps", 300 | "JIT:" + status.getInt(TXLiveConstants.NET_STATUS_NET_JITTER), 301 | "FPS:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_FPS), 302 | "GOP:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_GOP) + "s", 303 | "ARA:" + status.getInt(TXLiveConstants.NET_STATUS_AUDIO_BITRATE) + "Kbps", 304 | "QUE:" + status.getInt(TXLiveConstants.NET_STATUS_CODEC_CACHE) + "|" + status.getInt(TXLiveConstants.NET_STATUS_CACHE_SIZE), 305 | "DRP:" + status.getInt(TXLiveConstants.NET_STATUS_CODEC_DROP_CNT) + "|" + status.getInt(TXLiveConstants.NET_STATUS_DROP_SIZE), 306 | "VRA:" + status.getInt(TXLiveConstants.NET_STATUS_VIDEO_BITRATE) + "Kbps", 307 | "SVR:" + status.getString(TXLiveConstants.NET_STATUS_SERVER_IP), 308 | "AUDIO:" + status.getString(TXLiveConstants.NET_STATUS_AUDIO_INFO)); 309 | return str; 310 | } 311 | 312 | /** 313 | * 美颜回调 314 | */ 315 | @Override 316 | public void onBeautyParamsChange(BeautyDialogFragment.BeautyParams params, int key) { 317 | switch (key) { 318 | case BeautyDialogFragment.BEAUTYPARAM_BEAUTY: 319 | case BeautyDialogFragment.BEAUTYPARAM_WHITE: 320 | if (mLivePusher != null) { 321 | mLivePusher.setBeautyFilter(params.mBeautyStyle, params.mBeautyProgress, params.mWhiteProgress, params.mRuddyProgress); 322 | } 323 | break; 324 | case BeautyDialogFragment.BEAUTYPARAM_FACE_LIFT: 325 | if (mLivePusher != null) { 326 | mLivePusher.setFaceSlimLevel(params.mFaceLiftProgress); 327 | } 328 | break; 329 | case BeautyDialogFragment.BEAUTYPARAM_BIG_EYE: 330 | if (mLivePusher != null) { 331 | mLivePusher.setEyeScaleLevel(params.mBigEyeProgress); 332 | } 333 | break; 334 | case BeautyDialogFragment.BEAUTYPARAM_FILTER: 335 | if (mLivePusher != null) { 336 | mLivePusher.setFilter(TCUtils.getFilterBitmap(getResources(), params.mFilterIdx)); 337 | } 338 | break; 339 | case BeautyDialogFragment.BEAUTYPARAM_MOTION_TMPL: 340 | if (mLivePusher != null) { 341 | mLivePusher.setMotionTmpl(params.mMotionTmplPath); 342 | } 343 | break; 344 | case BeautyDialogFragment.BEAUTYPARAM_GREEN: 345 | if (mLivePusher != null) { 346 | mLivePusher.setGreenScreenFile(TCUtils.getGreenFileName(params.mGreenIdx)); 347 | } 348 | break; 349 | default: 350 | break; 351 | } 352 | } 353 | 354 | @Override 355 | public void onClick(View v) { 356 | switch (v.getId()) { 357 | 358 | case R.id.btn_message_input: 359 | break; 360 | 361 | case R.id.btnFaceBeauty: 362 | //美颜 363 | if (mBeautyDialogFragment.isAdded()) { 364 | mBeautyDialogFragment.dismiss(); 365 | } else { 366 | mBeautyDialogFragment.show(getSupportFragmentManager(), ""); 367 | } 368 | break; 369 | 370 | case R.id.btnCameraChange: 371 | //摄像头切换 372 | mLivePusher.switchCamera(); 373 | break; 374 | 375 | case R.id.btn_close: 376 | 377 | //关闭推流 378 | stopPublishRtmp(); 379 | finish(); 380 | break; 381 | 382 | case R.id.rtmproom_linkmic_btn: 383 | startLinkMic(); 384 | break; 385 | } 386 | } 387 | 388 | private void startLinkMic() { 389 | mBtnLinkMic.setEnabled(false); 390 | Toast.makeText(RTMPActivity.this, "等待主播接受......", 2).show(); 391 | 392 | 393 | } 394 | 395 | @Override 396 | protected void onResume() { 397 | super.onResume(); 398 | if (mTxCloudVideoView != null) { 399 | mTxCloudVideoView.onResume(); 400 | } 401 | 402 | if (mVideoPublish && mLivePusher != null && mVideoSrc == VIDEO_SRC_CAMERA) { 403 | mLivePusher.resumePusher(); 404 | mLivePusher.resumeBGM(); 405 | } 406 | } 407 | 408 | 409 | @Override 410 | public void onStop() { 411 | super.onStop(); 412 | if (mTxCloudVideoView != null) { 413 | mTxCloudVideoView.onPause(); 414 | } 415 | 416 | if (mVideoPublish && mLivePusher != null && mVideoSrc == VIDEO_SRC_CAMERA) { 417 | mLivePusher.pausePusher(); 418 | mLivePusher.pauseBGM(); 419 | } 420 | } 421 | 422 | @Override 423 | protected void onDestroy() { 424 | super.onDestroy(); 425 | stopPublishRtmp(); 426 | if (mTxCloudVideoView != null) { 427 | mTxCloudVideoView.onDestroy(); 428 | } 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/utils/BitmapUtils.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo.utils; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.util.TypedValue; 7 | 8 | /** 9 | * @author wapchief 10 | * @date 2018/3/24 11 | */ 12 | 13 | public class BitmapUtils { 14 | 15 | public static Bitmap decodeResource(Resources resources, int id) { 16 | TypedValue value = new TypedValue(); 17 | resources.openRawResource(id, value); 18 | BitmapFactory.Options opts = new BitmapFactory.Options(); 19 | opts.inTargetDensity = value.density; 20 | return BitmapFactory.decodeResource(resources, id, opts); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/utils/TCFrequeControl.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo.utils; 2 | 3 | /** 4 | * Created by carolsuo on 2016/9/21. 5 | * 频率控制类,用于控制一定时间内的触发次数不能过多 6 | * 规则为在设定时间内,最开始的nCounts次请求能触发;忽略后面超出的请求,距离上个时间段内首次触发时间超过设定时间后重启下一个时间段逻辑。 7 | */ 8 | 9 | public class TCFrequeControl { 10 | private int mCounts = 0; //设定时间内允许触发的次数 11 | private int mSeconds = 0; //设定的时间秒数 12 | private int mCurrentCounts = 0; //当前已经触发的次数 13 | private long mFirstTriggerTime = 0; //当前时间段内首次触发的时间 14 | 15 | public void init(int nCounts, int nSeconds) { 16 | this.mCounts = nCounts; 17 | this.mSeconds = nSeconds; 18 | this.mCurrentCounts = 0; 19 | this.mFirstTriggerTime = 0; 20 | } 21 | 22 | public boolean canTrigger() { 23 | long time = System.currentTimeMillis(); 24 | 25 | 26 | //重置首次触发时间和已经触发次数 27 | if (mFirstTriggerTime == 0 || time - mFirstTriggerTime > 1000*mSeconds) { 28 | mFirstTriggerTime = time; 29 | mCurrentCounts = 0; 30 | } 31 | 32 | //已经触发了mCounts次,本次不能触发 33 | if (mCurrentCounts >= mCounts) { 34 | return false; 35 | } 36 | 37 | ++mCurrentCounts; 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/view/TCInputTextMsgDialog.java: -------------------------------------------------------------------------------- 1 | package com.zjp.tencentvideo.view; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.graphics.PorterDuff; 6 | import android.graphics.Rect; 7 | import android.text.InputType; 8 | import android.text.TextUtils; 9 | import android.util.Log; 10 | import android.view.KeyEvent; 11 | import android.view.View; 12 | import android.view.inputmethod.InputMethodManager; 13 | import android.widget.Button; 14 | import android.widget.EditText; 15 | import android.widget.LinearLayout; 16 | import android.widget.RelativeLayout; 17 | import android.widget.TextView; 18 | import android.widget.Toast; 19 | 20 | import com.zjp.tencentvideo.R; 21 | 22 | 23 | /** 24 | * 文本输入框 25 | */ 26 | public class TCInputTextMsgDialog extends Dialog { 27 | 28 | public interface OnTextSendListener { 29 | 30 | void onTextSend(String msg, boolean tanmuOpen); 31 | } 32 | private TextView confirmBtn; 33 | private LinearLayout mBarrageArea; 34 | private EditText messageTextView; 35 | private static final String TAG = TCInputTextMsgDialog.class.getSimpleName(); 36 | private Context mContext; 37 | private InputMethodManager imm; 38 | private RelativeLayout rlDlg; 39 | private int mLastDiff = 0; 40 | private LinearLayout mConfirmArea; 41 | private OnTextSendListener mOnTextSendListener; 42 | private boolean mDanmuOpen = false; 43 | // private final String reg = "[`~@#$%^&*()-_+=|{}':;,/.<>¥…()—【】‘;:”“’。,、]"; 44 | // private Pattern pattern = Pattern.compile(reg); 45 | 46 | public TCInputTextMsgDialog(Context context, int theme) { 47 | super(context, theme); 48 | mContext = context; 49 | setContentView(R.layout.dialog_input_text); 50 | 51 | messageTextView = (EditText) findViewById(R.id.et_input_message); 52 | messageTextView.setInputType(InputType.TYPE_CLASS_TEXT); 53 | //修改下划线颜色 54 | messageTextView.getBackground().setColorFilter(context.getResources().getColor(R.color.transparent), PorterDuff.Mode.CLEAR); 55 | 56 | 57 | confirmBtn = (TextView) findViewById(R.id.confrim_btn); 58 | imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 59 | confirmBtn.setOnClickListener(new View.OnClickListener() { 60 | @Override 61 | public void onClick(View view) { 62 | String msg = messageTextView.getText().toString().trim(); 63 | if (!TextUtils.isEmpty(msg)) { 64 | 65 | mOnTextSendListener.onTextSend(msg, mDanmuOpen); 66 | imm.showSoftInput(messageTextView, InputMethodManager.SHOW_FORCED); 67 | imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0); 68 | messageTextView.setText(""); 69 | dismiss(); 70 | } else { 71 | Toast.makeText(mContext, "input can not be empty!", Toast.LENGTH_LONG).show(); 72 | } 73 | messageTextView.setText(null); 74 | } 75 | }); 76 | 77 | final Button barrageBtn = (Button) findViewById(R.id.barrage_btn); 78 | barrageBtn.setOnClickListener(new View.OnClickListener() { 79 | @Override 80 | public void onClick(View view) { 81 | mDanmuOpen = !mDanmuOpen; 82 | if (mDanmuOpen) { 83 | barrageBtn.setBackgroundResource(R.drawable.barrage_slider_on); 84 | } else { 85 | barrageBtn.setBackgroundResource(R.drawable.barrage_slider_off); 86 | } 87 | } 88 | }); 89 | 90 | mBarrageArea = (LinearLayout) findViewById(R.id.barrage_area); 91 | mBarrageArea.setOnClickListener(new View.OnClickListener() { 92 | @Override 93 | public void onClick(View v) { 94 | mDanmuOpen = !mDanmuOpen; 95 | if (mDanmuOpen) { 96 | barrageBtn.setBackgroundResource(R.drawable.barrage_slider_on); 97 | } else { 98 | barrageBtn.setBackgroundResource(R.drawable.barrage_slider_off); 99 | } 100 | } 101 | }); 102 | 103 | messageTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() { 104 | @Override 105 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 106 | switch (actionId) { 107 | case KeyEvent.KEYCODE_ENDCALL: 108 | case KeyEvent.KEYCODE_ENTER: 109 | if (messageTextView.getText().length() > 0) { 110 | // mOnTextSendListener.onTextSend("" + messageTextView.getText(), mDanmuOpen); 111 | //sendText("" + messageTextView.getText()); 112 | //imm.showSoftInput(messageTextView, InputMethodManager.SHOW_FORCED); 113 | imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0); 114 | // messageTextView.setText(""); 115 | dismiss(); 116 | } else { 117 | Toast.makeText(mContext, "input can not be empty!", Toast.LENGTH_LONG).show(); 118 | } 119 | return true; 120 | case KeyEvent.KEYCODE_BACK: 121 | dismiss(); 122 | return false; 123 | default: 124 | return false; 125 | } 126 | } 127 | }); 128 | 129 | mConfirmArea = (LinearLayout) findViewById(R.id.confirm_area); 130 | mConfirmArea.setOnClickListener(new View.OnClickListener() { 131 | @Override 132 | public void onClick(View v) { 133 | String msg = messageTextView.getText().toString().trim(); 134 | if (!TextUtils.isEmpty(msg)) { 135 | 136 | mOnTextSendListener.onTextSend(msg, mDanmuOpen); 137 | imm.showSoftInput(messageTextView, InputMethodManager.SHOW_FORCED); 138 | imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0); 139 | messageTextView.setText(""); 140 | dismiss(); 141 | } else { 142 | Toast.makeText(mContext, "input can not be empty!", Toast.LENGTH_LONG).show(); 143 | } 144 | messageTextView.setText(null); 145 | } 146 | }); 147 | 148 | messageTextView.setOnKeyListener(new View.OnKeyListener() { 149 | @Override 150 | public boolean onKey(View view, int i, KeyEvent keyEvent) { 151 | Log.d("My test", "onKey " + keyEvent.getCharacters()); 152 | return false; 153 | } 154 | }); 155 | 156 | rlDlg = (RelativeLayout) findViewById(R.id.rl_outside_view); 157 | rlDlg.setOnClickListener(new View.OnClickListener() { 158 | @Override 159 | public void onClick(View v) { 160 | if (v.getId() != R.id.rl_inputdlg_view) { 161 | 162 | dismiss(); 163 | } 164 | } 165 | }); 166 | 167 | final LinearLayout rldlgview = (LinearLayout) findViewById(R.id.rl_inputdlg_view); 168 | 169 | rldlgview.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 170 | @Override 171 | public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) { 172 | Rect r = new Rect(); 173 | //获取当前界面可视部分 174 | getWindow().getDecorView().getWindowVisibleDisplayFrame(r); 175 | //获取屏幕的高度 176 | int screenHeight = getWindow().getDecorView().getRootView().getHeight(); 177 | //此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数 178 | int heightDifference = screenHeight - r.bottom; 179 | 180 | if (heightDifference <= 0 && mLastDiff > 0){ 181 | //imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0); 182 | dismiss(); 183 | } 184 | mLastDiff = heightDifference; 185 | } 186 | }); 187 | rldlgview.setOnClickListener(new View.OnClickListener() { 188 | @Override 189 | public void onClick(View v) { 190 | imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0); 191 | dismiss(); 192 | } 193 | }); 194 | } 195 | 196 | public void setmOnTextSendListener(OnTextSendListener onTextSendListener) { 197 | this.mOnTextSendListener = onTextSendListener; 198 | } 199 | 200 | @Override 201 | public void dismiss() { 202 | super.dismiss(); 203 | //dismiss之前重置mLastDiff值避免下次无法打开 204 | mLastDiff = 0; 205 | } 206 | 207 | @Override 208 | public void show() { 209 | super.show(); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/view/like/TCAbstractPathAnimator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.zjp.tencentvideo.view.like; 17 | 18 | import android.content.res.Resources; 19 | import android.content.res.TypedArray; 20 | import android.graphics.Path; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import com.zjp.tencentvideo.R; 25 | 26 | import java.util.Random; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | 29 | 30 | /** 31 | * 飘心路径 32 | */ 33 | public abstract class TCAbstractPathAnimator { 34 | private final Random mRandom; 35 | protected final Config mConfig; 36 | 37 | 38 | public TCAbstractPathAnimator(Config config) { 39 | mConfig = config; 40 | mRandom = new Random(); 41 | } 42 | 43 | public float randomRotation() { 44 | return mRandom.nextFloat() * 28.6F - 14.3F; 45 | } 46 | 47 | public Path createPath(AtomicInteger counter, View view, int factor) { 48 | Random r = mRandom; 49 | int x = r.nextInt(mConfig.xRand); 50 | int x2 = r.nextInt(mConfig.xRand); 51 | int y = view.getHeight() - mConfig.initY; 52 | int y2 = counter.intValue() * 15 + mConfig.animLength * factor + r.nextInt(mConfig.animLengthRand); 53 | factor = y2 / mConfig.bezierFactor; 54 | x = mConfig.xPointFactor + x; 55 | x2 = mConfig.xPointFactor + x2; 56 | int y3 = y - y2; 57 | y2 = y - y2 / 2; 58 | Path p = new Path(); 59 | p.moveTo(mConfig.initX, y); 60 | p.cubicTo(mConfig.initX, y - factor, x, y2 + factor, x, y2); 61 | p.moveTo(x, y2); 62 | p.cubicTo(x, y2 - factor, x2, y3 + factor, x2, y3); 63 | return p; 64 | } 65 | 66 | public abstract void start(View child, ViewGroup parent); 67 | 68 | public static class Config { 69 | public int initX; 70 | public int initY; 71 | public int xRand; 72 | public int animLengthRand; 73 | public int bezierFactor; 74 | public int xPointFactor; 75 | public int animLength; 76 | public int heartWidth; 77 | public int heartHeight; 78 | public int animDuration; 79 | 80 | static public Config fromTypeArray(TypedArray typedArray, float x, float y, int pointx, int heartWidth, int heartHeight) { 81 | Config config = new Config(); 82 | Resources res = typedArray.getResources(); 83 | config.initX = (int) typedArray.getDimension(R.styleable.HeartLayout_initX, 84 | x); 85 | config.initY = (int) typedArray.getDimension(R.styleable.HeartLayout_initY, 86 | y); 87 | config.xRand = (int) typedArray.getDimension(R.styleable.HeartLayout_xRand, 88 | res.getDimensionPixelOffset(R.dimen.heart_anim_bezier_x_rand)); 89 | config.animLength = (int) typedArray.getDimension(R.styleable.HeartLayout_animLength, 90 | res.getDimensionPixelOffset(R.dimen.heart_anim_length));//动画长度 91 | config.animLengthRand = (int) typedArray.getDimension(R.styleable.HeartLayout_animLengthRand, 92 | res.getDimensionPixelOffset(R.dimen.heart_anim_length_rand)); 93 | config.bezierFactor = typedArray.getInteger(R.styleable.HeartLayout_bezierFactor, 94 | res.getInteger(R.integer.heart_anim_bezier_factor)); 95 | config.xPointFactor = pointx; 96 | // config.heartWidth = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_width, 97 | // res.getDimensionPixelOffset(R.dimen.heart_size_width));//动画图片宽度 98 | // config.heartHeight = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_height, 99 | // res.getDimensionPixelOffset(R.dimen.heart_size_height));//动画图片高度 100 | config.heartWidth = heartWidth; 101 | config.heartHeight = heartHeight; 102 | config.animDuration = typedArray.getInteger(R.styleable.HeartLayout_anim_duration, 103 | res.getInteger(R.integer.anim_duration));//持续期 104 | return config; 105 | } 106 | 107 | 108 | } 109 | 110 | 111 | } 112 | 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/view/like/TCHeartLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.zjp.tencentvideo.view.like; 17 | 18 | import android.content.Context; 19 | import android.content.res.TypedArray; 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.graphics.drawable.BitmapDrawable; 23 | import android.graphics.drawable.Drawable; 24 | import android.util.AttributeSet; 25 | import android.view.LayoutInflater; 26 | import android.widget.RelativeLayout; 27 | 28 | 29 | import com.zjp.tencentvideo.R; 30 | 31 | import java.util.Random; 32 | 33 | 34 | /** 35 | * 飘心动画界面布局类 36 | * 通过动画控制每个心形界面的显示 37 | * TCPathAnimator 控制显示路径 38 | * TCHeartView 单个心形界面 39 | */ 40 | public class TCHeartLayout extends RelativeLayout { 41 | 42 | private TCAbstractPathAnimator mAnimator; 43 | private int defStyleAttr = 0; 44 | 45 | private int textHight; 46 | private int dHeight; 47 | private int dWidth; 48 | private int initX; 49 | private int pointx; 50 | 51 | public TCHeartLayout(Context context, AttributeSet attrs) { 52 | super(context, attrs); 53 | findViewById(context); 54 | initHeartDrawable(); 55 | init(attrs, defStyleAttr); 56 | } 57 | 58 | private void findViewById(Context context) { 59 | LayoutInflater.from(context).inflate(R.layout.layout_periscope, this); 60 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_like_png); 61 | dHeight = bitmap.getWidth(); 62 | dWidth = bitmap.getHeight(); 63 | textHight = sp2px(getContext(), 20) + dHeight / 2; 64 | 65 | pointx = dWidth;//随机上浮方向的x坐标 66 | 67 | bitmap.recycle(); 68 | } 69 | 70 | private static int sp2px(Context context, float spValue) { 71 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 72 | return (int) (spValue * fontScale + 0.5f); 73 | } 74 | 75 | 76 | private void init(AttributeSet attrs, int defStyleAttr) { 77 | final TypedArray a = getContext().obtainStyledAttributes( 78 | attrs, R.styleable.HeartLayout, defStyleAttr, 0); 79 | 80 | //todo:获取确切值 81 | initX = 30; 82 | if (pointx <= initX && pointx >= 0) { 83 | pointx -= 10; 84 | } else if (pointx >= -initX && pointx <= 0) { 85 | pointx += 10; 86 | } else { 87 | 88 | pointx = initX; 89 | } 90 | 91 | mAnimator = new TCPathAnimator( 92 | TCAbstractPathAnimator.Config.fromTypeArray(a, initX, textHight, pointx, dWidth, dHeight)); 93 | a.recycle(); 94 | } 95 | 96 | @Override 97 | public void clearAnimation() { 98 | for (int i = 0; i < getChildCount(); i++) { 99 | getChildAt(i).clearAnimation(); 100 | } 101 | removeAllViews(); 102 | } 103 | public void resourceLoad(){ 104 | mHearts = new Bitmap[drawableIds.length]; 105 | mHeartsDrawable = new BitmapDrawable[drawableIds.length]; 106 | for (int i =0;i < drawableIds.length;i++){ 107 | mHearts[i] = BitmapFactory.decodeResource(getResources(), drawableIds[i]); 108 | mHeartsDrawable[i] = new BitmapDrawable(getResources(), mHearts[i]); 109 | } 110 | } 111 | private static int[] drawableIds = new int[]{R.drawable.heart0, R.drawable.heart1, R.drawable.heart2, R.drawable.heart3, R.drawable.heart4, R.drawable.heart5, R.drawable.heart6, R.drawable.heart7, R.drawable.heart8,}; 112 | private Random mRandom = new Random(); 113 | private static Drawable[] sDrawables; 114 | private Bitmap[] mHearts; 115 | private BitmapDrawable[] mHeartsDrawable; 116 | private void initHeartDrawable() { 117 | int size = drawableIds.length; 118 | sDrawables = new Drawable[size]; 119 | for (int i = 0 ; i < size; i++) { 120 | sDrawables[i] = getResources().getDrawable(drawableIds[i]); 121 | } 122 | resourceLoad(); 123 | } 124 | 125 | public void addFavor() { 126 | TCHeartView heartView = new TCHeartView(getContext()); 127 | heartView.setDrawable(mHeartsDrawable[mRandom.nextInt(8)]); 128 | // heartView.setImageDrawable(sDrawables[random.nextInt(8)]); 129 | // init(attrs, defStyleAttr); 130 | mAnimator.start(heartView, this); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/view/like/TCHeartView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.zjp.tencentvideo.view.like; 17 | 18 | import android.annotation.SuppressLint; 19 | import android.content.Context; 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.graphics.Canvas; 23 | import android.graphics.Paint; 24 | import android.graphics.PorterDuff; 25 | import android.graphics.PorterDuffColorFilter; 26 | import android.graphics.drawable.BitmapDrawable; 27 | import android.util.AttributeSet; 28 | import android.widget.ImageView; 29 | 30 | import com.zjp.tencentvideo.R; 31 | 32 | 33 | /** 34 | * 飘心动画心形界面类 35 | * 一个TCHeartView代表一个心形,由heart.png组成 36 | */ 37 | @SuppressLint("AppCompatCustomView") 38 | public class TCHeartView extends ImageView { 39 | 40 | private static final Paint sPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 41 | private int mHeartResId = R.drawable.heart0; 42 | private int mHeartBorderResId = R.drawable.heart1; 43 | private static Bitmap sHeart; 44 | private static Bitmap sHeartBorder; 45 | private static final Canvas sCanvas = new Canvas(); 46 | 47 | public TCHeartView(Context context, AttributeSet attrs) { 48 | super(context, attrs); 49 | } 50 | 51 | public TCHeartView(Context context, AttributeSet attrs, int defStyleAttr) { 52 | super(context, attrs, defStyleAttr); 53 | } 54 | 55 | public TCHeartView(Context context) { 56 | super(context); 57 | } 58 | 59 | public void setDrawable(BitmapDrawable bitmap) { 60 | setImageDrawable(bitmap); 61 | } 62 | 63 | public void setColor(int color) { 64 | Bitmap heart = createHeart(color); 65 | setImageDrawable(new BitmapDrawable(getResources(), heart)); 66 | } 67 | 68 | public void setColorAndDrawables(int color, int heartResId, int heartBorderResId) { 69 | if (heartResId != mHeartResId) { 70 | sHeart = null; 71 | } 72 | if (heartBorderResId != mHeartBorderResId) { 73 | sHeartBorder = null; 74 | } 75 | mHeartResId = heartResId; 76 | mHeartBorderResId = heartBorderResId; 77 | setColor(color); 78 | } 79 | 80 | public Bitmap createHeart(int color) { 81 | if (sHeart == null) { 82 | sHeart = BitmapFactory.decodeResource(getResources(), mHeartResId); 83 | } 84 | if (sHeartBorder == null) { 85 | sHeartBorder = BitmapFactory.decodeResource(getResources(), mHeartBorderResId); 86 | } 87 | Bitmap heart = sHeart; 88 | Bitmap heartBorder = sHeartBorder; 89 | Bitmap bm = createBitmapSafely(heartBorder.getWidth(), heartBorder.getHeight()); 90 | if (bm == null) { 91 | return null; 92 | } 93 | Canvas canvas = sCanvas; 94 | canvas.setBitmap(bm); 95 | Paint p = sPaint; 96 | canvas.drawBitmap(heartBorder, 0, 0, p); 97 | p.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); 98 | float dx = (heartBorder.getWidth() - heart.getWidth()) / 2f; 99 | float dy = (heartBorder.getHeight() - heart.getHeight()) / 2f; 100 | canvas.drawBitmap(heart, dx, dy, p); 101 | p.setColorFilter(null); 102 | canvas.setBitmap(null); 103 | return bm; 104 | } 105 | 106 | private static Bitmap createBitmapSafely(int width, int height) { 107 | try { 108 | return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 109 | } catch (OutOfMemoryError error) { 110 | error.printStackTrace(); 111 | } 112 | return null; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/zjp/tencentvideo/view/like/TCPathAnimator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.zjp.tencentvideo.view.like; 17 | 18 | import android.graphics.Matrix; 19 | import android.graphics.Path; 20 | import android.graphics.PathMeasure; 21 | import android.os.Handler; 22 | import android.os.Looper; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.view.animation.Animation; 26 | import android.view.animation.LinearInterpolator; 27 | import android.view.animation.Transformation; 28 | 29 | import java.util.HashMap; 30 | import java.util.Random; 31 | import java.util.concurrent.atomic.AtomicInteger; 32 | 33 | /** 34 | * 飘心路径动画器 35 | */ 36 | public class TCPathAnimator extends TCAbstractPathAnimator { 37 | private final AtomicInteger mCounter = new AtomicInteger(0); 38 | private Handler mHandler; 39 | 40 | //路径的缓存 41 | private final static int MAX_PATH_COUNTS = 10; //最多生成的路径数目 42 | private int mCurrentPathCounts = 0; //已经生成的路径数目 43 | private HashMap mPathVec = null; //已经生成的路径缓存 44 | private final Random mRandom; //随机数 45 | 46 | public TCPathAnimator(Config config) { 47 | super(config); 48 | mHandler = new Handler(Looper.getMainLooper()); 49 | mPathVec = new HashMap<>(); 50 | mRandom = new Random(); 51 | } 52 | 53 | @Override 54 | public void start(final View child, final ViewGroup parent) { 55 | parent.addView(child, new ViewGroup.LayoutParams(mConfig.heartWidth, mConfig.heartHeight)); 56 | 57 | Path path; 58 | ++mCurrentPathCounts; 59 | 60 | //如果已经生成的路径数目超过最大设定,就从路径缓存中随机取一个路径用于绘制,否则新生成一个 61 | if (mCurrentPathCounts > MAX_PATH_COUNTS) { 62 | path = mPathVec.get(Math.abs(mRandom.nextInt()%MAX_PATH_COUNTS) + 1); 63 | } else { 64 | path = createPath(mCounter, parent, 2); 65 | mPathVec.put(mCurrentPathCounts, path); 66 | } 67 | 68 | FloatAnimation anim = new FloatAnimation(path, randomRotation(), parent, child); 69 | anim.setDuration(mConfig.animDuration); 70 | anim.setInterpolator(new LinearInterpolator()); 71 | anim.setAnimationListener(new Animation.AnimationListener() { 72 | @Override 73 | public void onAnimationEnd(Animation animation) { 74 | mHandler.post(new Runnable() { 75 | @Override 76 | public void run() { 77 | parent.removeView(child); 78 | } 79 | }); 80 | mCounter.decrementAndGet(); 81 | } 82 | 83 | @Override 84 | public void onAnimationRepeat(Animation animation) { 85 | 86 | } 87 | 88 | @Override 89 | public void onAnimationStart(Animation animation) { 90 | mCounter.incrementAndGet(); 91 | } 92 | }); 93 | child.startAnimation(anim); 94 | } 95 | 96 | static class FloatAnimation extends Animation { 97 | private PathMeasure mPm; 98 | private View mView; 99 | private float mDistance; 100 | private float mRotation; 101 | 102 | public FloatAnimation(Path path, float rotation, View parent, View child) { 103 | mPm = new PathMeasure(path, false); 104 | mDistance = mPm.getLength(); 105 | mView = child; 106 | mRotation = rotation; 107 | parent.setLayerType(View.LAYER_TYPE_HARDWARE, null); 108 | } 109 | 110 | @Override 111 | protected void applyTransformation(float factor, Transformation transformation) { 112 | Matrix matrix = transformation.getMatrix(); 113 | mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG); 114 | mView.setRotation(mRotation * factor); 115 | float scale = 1F; 116 | if (3000.0F * factor < 200.0F) { 117 | scale = scale(factor, 0.0D, 0.06666667014360428D, 0.20000000298023224D, 1.100000023841858D); 118 | } else if (3000.0F * factor < 300.0F) { 119 | scale = scale(factor, 0.06666667014360428D, 0.10000000149011612D, 1.100000023841858D, 1.0D); 120 | } 121 | mView.setScaleX(scale); 122 | mView.setScaleY(scale); 123 | transformation.setAlpha(1.0F - factor); 124 | } 125 | } 126 | 127 | private static float scale(double a, double b, double c, double d, double e) { 128 | return (float) ((a - b) / (c - b) * (e - d) + d); 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /app/src/main/res/anim/load_progress_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /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/again.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/again.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/barrage_slider_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/barrage_slider_off.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/barrage_slider_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/barrage_slider_on.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/beautiful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/beautiful.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/beautiful_press.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/beautiful_press.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/beauty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/beauty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_back_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/btn_back_gray.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/camera_change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/camera_change.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/face.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon_beauty_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/icon_beauty_png.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon_like_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/icon_like_png.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon_log_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/icon_log_off.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon_message_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/icon_message_png.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon_share_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/icon_share_png.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon_switch_camera_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/icon_switch_camera_on.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/linkmic_on_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/linkmic_on_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/loading_circle_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/loading_circle_progress.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/play_seekbar_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/play_seekbar_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/text_accent_gray_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xhdpi/video.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawable_face_beauty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawable_face_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawable_face_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawable_face_move.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/fennen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/fennen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_fennen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_fennen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_huaijiu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_huaijiu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_landiao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_landiao.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_langman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_langman.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_press.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_press.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_qingliang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_qingliang.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_qingxin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_qingxin.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_rixi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_rixi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_selected.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_weimei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_weimei.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/filter_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/filter_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/greens.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/greens_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/greens_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/greens_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/greens_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/greens_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/greens_no.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/greens_press.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/greens_press.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/greens_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/greens_selected.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart10.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart11.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart4.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart5.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart6.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart7.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart8.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/heart9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/huaijiu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/huaijiu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/landiao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/landiao.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/landscape.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/langman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/langman.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/linkmic_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/linkmic_start.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/liveshow_item_dot.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/move.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/move_press.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/move_press.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/orginal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/orginal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/play_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/play_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/play_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/play_start.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/portrait.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/progress_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/progress_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/progress_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/progress_front.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/progress_primary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/progress_primary.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/qingliang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/qingliang.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/qingxin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/qingxin.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/rixi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/rixi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/start_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/start_record.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/weimei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable-xxhdpi/weimei.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_send_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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/drawable/play_seekbar_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jsonjia/TencentVideoTest/5fc0be40c243aaff6e7f0c652c37c70c31db75ca/app/src/main/res/drawable/progress_bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/pusher_info_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/record_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/seekbar_progress_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_live.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 19 | 20 | 27 | 28 | 39 | 40 | 46 | 47 | 51 | 52 | 60 | 61 | 62 | 66 | 67 | 73 | 74 | 82 | 83 | 89 | 90 | 94 | 95 | 99 | 100 | 106 | 107 | 108 | 112 | 113 |