├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dbnavigator.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── appcompat_v7_23_1_1.xml │ ├── design_23_1_1.xml │ ├── hamcrest_core_1_3.xml │ ├── junit_4_12.xml │ ├── recyclerview_v7_23_1_1.xml │ ├── support_annotations_23_1_1.xml │ └── support_v4_23_1_1.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── LICENSE.TXT ├── README.md ├── VideoChatHeads.iml ├── android-facebook-like-chat-head-master.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── takeoffandroid │ │ └── videochatheads │ │ ├── activities │ │ └── MainActivity.java │ │ ├── services │ │ └── VideoChatHeadService.java │ │ ├── utils │ │ ├── Constants.java │ │ └── Utils.java │ │ └── views │ │ ├── GLRoundedGeometry.java │ │ ├── MultiSampleEGLConfigChooser.java │ │ └── VideoSurfaceView.java │ └── res │ ├── drawable │ ├── background_shade.png │ ├── drawable_circle_white.xml │ ├── ic_close.png │ ├── ic_pause.xml │ └── ic_play.xml │ ├── layout │ ├── activity_main.xml │ ├── view_layout_chat_heads.xml │ └── view_layout_close.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── raw │ └── sample_video.mp4 │ ├── values-v21 │ └── styles.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── projectFilesBackup └── .idea │ └── workspace.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | lt application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | 34 | 35 | 36 | 37 | .idea/gradle.xml 38 | 39 | .idea/gradle.xml 40 | 41 | .idea/libraries/animated_vector_drawable_23_2_1.xml 42 | 43 | .idea/libraries/appcompat_v7_23_2_1.xml 44 | 45 | .idea/libraries/animated_vector_drawable_23_2_1.xml 46 | 47 | .idea/libraries/design_23_2_1.xml 48 | 49 | .idea/gradle.xml 50 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | VideoChatHeads -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dbnavigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/libraries/appcompat_v7_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/design_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/recyclerview_v7_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/support_annotations_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-VideoChatHeads-brightgreen.svg?style=flat)]() 2 | 3 | Buy Me a Coffee at ko-fi.com 4 | 5 | # VideoChatHeads 6 | 7 | Why this repo? 8 | -------------- 9 | 10 | This repo enables you to show videos instead of usual profile picture for chat heads. Inspired from Facebook's Video profile picture, I decided to replicate its implementation to show videos in profile picture and to create chat heads for the same. 11 | 12 | 13 | 14 | 15 | 16 | > **Note:** This repo is a custom implementation for VideoChatHeads. Any kinds of suggestions or pull request are welcome and appreciated. 17 | 18 | ``` 19 | This is free and unencumbered software released into the public domain. 20 | 21 | Anyone is free to copy, modify, publish, use, compile, sell, or 22 | distribute this software, either in source code form or as a compiled 23 | binary, for any purpose, commercial or non-commercial, and by any 24 | means. 25 | 26 | In jurisdictions that recognize copyright laws, the author or authors 27 | of this software dedicate any and all copyright interest in the 28 | software to the public domain. We make this dedication for the benefit 29 | of the public at large and to the detriment of our heirs and 30 | successors. We intend this dedication to be an overt act of 31 | relinquishment in perpetuity of all present and future rights to this 32 | software under copyright law. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 35 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 36 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 37 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 38 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 39 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 40 | OTHER DEALINGS IN THE SOFTWARE. 41 | 42 | For more information, please refer to 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /VideoChatHeads.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /android-facebook-like-chat-head-master.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.takeoffandroid.videochatheads" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | compile 'com.android.support:design:23.1.1' 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Program Files\Eclipse\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/videochatheads/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.videochatheads.activities; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.provider.Settings; 8 | import android.support.design.widget.FloatingActionButton; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.Toolbar; 11 | import android.view.View; 12 | 13 | import com.takeoffandroid.videochatheads.R; 14 | import com.takeoffandroid.videochatheads.services.VideoChatHeadService; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | 18 | // UI 19 | private Toolbar toolbar; 20 | private FloatingActionButton fab; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | 27 | toolbar = (Toolbar) findViewById(R.id.toolbar_activity_main); 28 | 29 | setUiSettings(); 30 | } 31 | 32 | @Override 33 | protected void onResume() { 34 | super.onResume(); 35 | 36 | initListeners(); 37 | 38 | } 39 | 40 | private void initListeners() { 41 | 42 | if(Build.VERSION.SDK_INT >= 23) { 43 | if (!Settings.canDrawOverlays(MainActivity.this)) { 44 | Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 45 | Uri.parse("package:" + getPackageName())); 46 | startActivityForResult(intent, 1234); 47 | }else{ 48 | Intent i = VideoChatHeadService.getIntent(MainActivity.this); 49 | startService(i); 50 | MainActivity.this.finish(); 51 | } 52 | } 53 | else 54 | { 55 | Intent i = VideoChatHeadService.getIntent(MainActivity.this); 56 | startService(i); 57 | MainActivity.this.finish(); 58 | } 59 | 60 | } 61 | 62 | @Override 63 | public void onActivityReenter(int resultCode, Intent data) { 64 | 65 | if(resultCode == 1234){ 66 | 67 | Intent i = VideoChatHeadService.getIntent(MainActivity.this); 68 | startService(i); 69 | MainActivity.this.finish(); 70 | } 71 | super.onActivityReenter(resultCode, data); 72 | } 73 | 74 | private void setUiSettings() { 75 | setSupportActionBar(toolbar); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/videochatheads/services/VideoChatHeadService.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.videochatheads.services; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Service; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.graphics.PixelFormat; 8 | import android.media.MediaPlayer; 9 | import android.os.Build; 10 | import android.os.Handler; 11 | import android.os.IBinder; 12 | import android.os.Vibrator; 13 | import android.support.annotation.Nullable; 14 | import android.util.DisplayMetrics; 15 | import android.util.Log; 16 | import android.view.Gravity; 17 | import android.view.LayoutInflater; 18 | import android.view.MotionEvent; 19 | import android.view.SurfaceHolder; 20 | import android.view.View; 21 | import android.view.WindowManager; 22 | import android.widget.ImageView; 23 | import android.widget.Toast; 24 | 25 | import com.takeoffandroid.videochatheads.R; 26 | import com.takeoffandroid.videochatheads.utils.Constants; 27 | import com.takeoffandroid.videochatheads.utils.Utils; 28 | import com.takeoffandroid.videochatheads.views.VideoSurfaceView; 29 | 30 | import java.io.IOException; 31 | 32 | 33 | public class VideoChatHeadService extends Service implements SurfaceHolder.Callback { 34 | 35 | // constants 36 | public static final String BASIC_TAG = VideoChatHeadService.class.getName(); 37 | 38 | // variables 39 | private WindowManager mWindowManager; 40 | private Vibrator mVibrator; 41 | private WindowManager.LayoutParams mVideoViewParams; 42 | private WindowManager.LayoutParams mCloseViewParams; 43 | private int windowHeight; 44 | private int windowWidth; 45 | 46 | private View closeView, chatHeadsView; 47 | private LayoutInflater liClose, liChatHeads; 48 | 49 | private ImageView ivCloseView, imgPlayPause; 50 | 51 | 52 | private VideoSurfaceView[] mVideoSurfaceView = new VideoSurfaceView[1]; 53 | private MediaPlayer mediaPlayer; 54 | private boolean isMediaPrepared, isPlaying; 55 | 56 | 57 | public static Intent getIntent(Context context) { 58 | Intent intent = new Intent(context, VideoChatHeadService.class); 59 | return intent; 60 | } 61 | 62 | // methods 63 | @Override 64 | public void onCreate() { 65 | super.onCreate(); 66 | } 67 | 68 | @Override 69 | public int onStartCommand(Intent intent, int flags, int startId) { 70 | showChatHeades(); 71 | 72 | return START_STICKY; 73 | } 74 | 75 | private void showChatHeades() { 76 | mWindowManager = (WindowManager) getSystemService(Service.WINDOW_SERVICE); 77 | mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 78 | liChatHeads = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); 79 | 80 | if (chatHeadsView != null) { 81 | mWindowManager.removeView(chatHeadsView); 82 | mVideoSurfaceView[0] = null; 83 | } 84 | 85 | mVideoViewParams = new WindowManager.LayoutParams( 86 | WindowManager.LayoutParams.WRAP_CONTENT, 87 | WindowManager.LayoutParams.WRAP_CONTENT, 88 | WindowManager.LayoutParams.TYPE_PHONE, 89 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, 90 | 91 | PixelFormat.TRANSLUCENT); 92 | 93 | 94 | DisplayMetrics displaymetrics = new DisplayMetrics(); 95 | mWindowManager.getDefaultDisplay().getMetrics(displaymetrics); 96 | windowHeight = displaymetrics.heightPixels; 97 | windowWidth = displaymetrics.widthPixels; 98 | 99 | mVideoViewParams.gravity = Gravity.TOP | Gravity.RIGHT; 100 | mVideoViewParams.height = Constants.VIDEO_VIEW_CIRCLE_SIZE; 101 | mVideoViewParams.width = Constants.VIDEO_VIEW_CIRCLE_SIZE; 102 | 103 | 104 | // final int radius = getResources() 105 | // .getDimensionPixelOffset(R.dimen.corner_radius_video); 106 | 107 | 108 | 109 | chatHeadsView = liChatHeads.inflate(R.layout.view_layout_chat_heads, null); 110 | 111 | mVideoSurfaceView[0] = (VideoSurfaceView) chatHeadsView.findViewById(R.id.video_surface_view); 112 | 113 | imgPlayPause = (ImageView) chatHeadsView.findViewById(R.id.img_play_pause); 114 | 115 | 116 | mVideoSurfaceView[0].setCornerRadius(Constants.VIDEO_VIEW_CORNER_RADIUS); 117 | 118 | mVideoViewParams.x = 0; 119 | mVideoViewParams.y = 50; 120 | 121 | mWindowManager.addView(chatHeadsView, mVideoViewParams); 122 | 123 | mediaPlayer = new MediaPlayer(); 124 | final VideoSurfaceView surfaceView = mVideoSurfaceView[0]; 125 | final String dataSource = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"; 126 | try { 127 | mediaPlayer.setDataSource(dataSource); 128 | // the video view will take care of calling prepare and attaching the surface once 129 | // it becomes available 130 | mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 131 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 132 | @Override 133 | public void onPrepared(MediaPlayer mp) { 134 | Utils.setBackground(VideoChatHeadService.this, imgPlayPause, R.drawable.ic_play); 135 | 136 | Handler handler = new Handler(); 137 | 138 | handler.postDelayed(new Runnable() { 139 | @Override 140 | public void run() { 141 | 142 | imgPlayPause.setVisibility(View.GONE); 143 | isMediaPrepared = true; 144 | 145 | isPlaying = true; 146 | mediaPlayer.start(); 147 | surfaceView.setVideoAspectRatio((float) mediaPlayer.getVideoWidth() / 148 | (float) mediaPlayer.getVideoHeight()); 149 | 150 | 151 | } 152 | }, 1000); 153 | 154 | 155 | } 156 | 157 | 158 | 159 | }); 160 | 161 | surfaceView.setMediaPlayer(mediaPlayer); 162 | } catch (IOException e) { 163 | e.printStackTrace(); 164 | mediaPlayer.release(); 165 | } 166 | addViewOnTouchListener(); 167 | } 168 | 169 | 170 | private void addViewOnTouchListener() { 171 | 172 | 173 | chatHeadsView.setOnTouchListener(new View.OnTouchListener() { 174 | private int initialX; 175 | private int initialY; 176 | private float initialTouchX; 177 | private float initialTouchY; 178 | 179 | @Override 180 | public boolean onTouch(View v, MotionEvent event) { 181 | switch (event.getAction()) { 182 | case MotionEvent.ACTION_DOWN: 183 | 184 | 185 | initialX = mVideoViewParams.x; 186 | initialY = mVideoViewParams.y; 187 | initialTouchX = event.getRawX(); 188 | initialTouchY = event.getRawY(); 189 | // add closeview when moving video view 190 | addCloseView(); 191 | 192 | return true; 193 | case MotionEvent.ACTION_UP: 194 | 195 | if ((Math.abs(initialTouchX - event.getRawX()) < 5) && (Math.abs(initialTouchY - event.getRawY()) < 5)) { 196 | 197 | 198 | if (isMediaPrepared) { 199 | if (isPlaying) { 200 | 201 | Toast.makeText(VideoChatHeadService.this, "Pause Video", Toast.LENGTH_SHORT).show(); 202 | 203 | isPlaying = false; 204 | imgPlayPause.setVisibility(View.VISIBLE); 205 | // Utils.setBackground(VideoChatHeadService.this, imgPlayPause, R.drawable.ic_pause); 206 | 207 | imgPlayPause.setImageResource(R.drawable.ic_pause); 208 | mediaPlayer.pause(); 209 | 210 | } else { 211 | 212 | isPlaying = true; 213 | 214 | Toast.makeText(VideoChatHeadService.this, "Play Video", Toast.LENGTH_SHORT).show(); 215 | 216 | imgPlayPause.setImageResource(R.drawable.ic_play); 217 | 218 | new Handler().postDelayed(new Runnable() { 219 | @Override 220 | public void run() { 221 | imgPlayPause.setVisibility(View.GONE); 222 | mediaPlayer.start(); 223 | 224 | } 225 | }, 1000); 226 | 227 | 228 | } 229 | } 230 | } 231 | 232 | int centerOfScreenByX = windowWidth / 2; 233 | 234 | // remove video view when the it is in the close view area 235 | if ((mVideoViewParams.y > windowHeight - closeView.getHeight() - chatHeadsView.getHeight()) && 236 | ((mVideoViewParams.x > centerOfScreenByX - ivCloseView.getWidth() - chatHeadsView.getWidth() / 2) && (mVideoViewParams.x < centerOfScreenByX + ivCloseView.getWidth() / 2))) { 237 | mVibrator.vibrate(100); 238 | 239 | 240 | if (isMediaPrepared) { 241 | mediaPlayer.stop(); 242 | 243 | } 244 | 245 | stopSelf(); 246 | } 247 | 248 | 249 | // always remove close view ImageView when video view is dropped 250 | mWindowManager.removeView(closeView); 251 | ivCloseView = null; 252 | 253 | return true; 254 | case MotionEvent.ACTION_MOVE: 255 | // move videoview ImageView 256 | 257 | mVideoViewParams.x = initialX + (int) (initialTouchX - event.getRawX()); 258 | mVideoViewParams.y = initialY + (int) (event.getRawY() - initialTouchY); 259 | mWindowManager.updateViewLayout(chatHeadsView, mVideoViewParams); 260 | return true; 261 | } 262 | return false; 263 | } 264 | }); 265 | } 266 | 267 | private void addCloseView() { 268 | 269 | liClose = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); 270 | 271 | // add close view ImageView centered on the bottom of the screen 272 | mCloseViewParams = new WindowManager.LayoutParams( 273 | WindowManager.LayoutParams.WRAP_CONTENT, 274 | WindowManager.LayoutParams.WRAP_CONTENT, 275 | WindowManager.LayoutParams.TYPE_PHONE, 276 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 277 | PixelFormat.TRANSLUCENT); 278 | 279 | mCloseViewParams.gravity = Gravity.BOTTOM | Gravity.CENTER; 280 | mCloseViewParams.height = 400; 281 | mCloseViewParams.width = WindowManager.LayoutParams.MATCH_PARENT; 282 | 283 | closeView = liClose.inflate(R.layout.view_layout_close, null); 284 | 285 | ivCloseView = (ImageView) closeView.findViewById(R.id.img_close); 286 | 287 | mCloseViewParams.x = 0; 288 | mCloseViewParams.y = 0; 289 | 290 | mWindowManager.addView(closeView, mCloseViewParams); 291 | } 292 | 293 | @Nullable 294 | @Override 295 | public IBinder onBind(Intent intent) { 296 | return null; 297 | } 298 | 299 | @Override 300 | public void onDestroy() { 301 | 302 | // remove views on destroy! 303 | if (chatHeadsView != null) { 304 | mWindowManager.removeView(chatHeadsView); 305 | mVideoSurfaceView[0] = null; 306 | 307 | imgPlayPause = null; 308 | } 309 | 310 | if (ivCloseView != null) { 311 | mWindowManager.removeView(closeView); 312 | ivCloseView = null; 313 | } 314 | 315 | if (isMediaPrepared) { 316 | mediaPlayer.stop(); 317 | } 318 | 319 | super.onDestroy(); 320 | 321 | } 322 | 323 | @Override 324 | public void surfaceCreated(SurfaceHolder holder) { 325 | } 326 | 327 | @Override 328 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 329 | 330 | } 331 | 332 | @Override 333 | public void surfaceDestroyed(SurfaceHolder holder) { 334 | 335 | 336 | } 337 | 338 | 339 | } 340 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/videochatheads/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.videochatheads.utils; 2 | 3 | /** 4 | * Created by chandrasekar on 14/11/16. 5 | */ 6 | 7 | public class Constants { 8 | 9 | public static final int VIDEO_VIEW_CIRCLE_SIZE = 200; 10 | 11 | public static final int VIDEO_VIEW_CORNER_RADIUS = 102; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/videochatheads/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.videochatheads.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.os.Build; 6 | import android.util.DisplayMetrics; 7 | import android.util.Log; 8 | import android.util.TypedValue; 9 | import android.widget.ImageView; 10 | 11 | import com.takeoffandroid.videochatheads.R; 12 | 13 | /** 14 | * Created by chandrasekar on 28/10/16. 15 | */ 16 | 17 | public class Utils { 18 | 19 | 20 | public static void setBackground(Context context, ImageView imageView, int drawableID) { 21 | if (Build.VERSION.SDK_INT >= 21) { 22 | imageView.setBackground(context.getResources().getDrawable(drawableID,null)); 23 | }else if(Build.VERSION.SDK_INT >= 16){ 24 | imageView.setBackground(context.getResources().getDrawable(drawableID)); 25 | }else { 26 | imageView.setBackgroundDrawable(context.getResources().getDrawable(drawableID)); 27 | } 28 | 29 | } 30 | 31 | public static float dipToPixels(Context context, float dipValue) { 32 | DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 33 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, dipValue, metrics); 34 | 35 | 36 | 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/videochatheads/views/GLRoundedGeometry.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.videochatheads.views; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.RectF; 5 | import android.support.annotation.NonNull; 6 | 7 | 8 | public class GLRoundedGeometry { 9 | 10 | // The key points of the geometry 11 | private float[] mLeftTop = new float[2]; 12 | private float[] mLeftBottom = new float[2]; 13 | private float[] mTopLeft = new float[2]; 14 | private float[] mTopRight = new float[2]; 15 | private float[] mRightTop = new float[2]; 16 | private float[] mRightBottom = new float[2]; 17 | private float[] mBottomLeft = new float[2]; 18 | private float[] mBottomRight = new float[2]; 19 | 20 | private float[] mInnerTopLeft = new float[2]; 21 | private float[] mInnerTopRight = new float[2]; 22 | private float[] mInnerBottomRight = new float[2]; 23 | private float[] mInnerBottomLeft = new float[2]; 24 | 25 | private float[] mTopLeftRadius = new float[2]; 26 | private float[] mTopRightRadius = new float[2]; 27 | private float[] mBottomRightRadius = new float[2]; 28 | private float[] mBottomLeftRadius = new float[2]; 29 | 30 | /** 31 | * @see #generateVertexData(RectF, RectF, Point, float) 32 | */ 33 | @NonNull 34 | public GeometryArrays generateVertexData(@NonNull RectF radii, @NonNull RectF viewPortGLBounds, 35 | @NonNull Point viewPortPxSize) { 36 | return generateVertexData(radii, viewPortGLBounds, viewPortPxSize, 0f); 37 | } 38 | 39 | /** 40 | * Generates a {@link GeometryArrays} object with arrays containing the resulting geometry 41 | * vertices and the corresponding triangle indexes. 42 | * 43 | * @param radii the corner radius of each corner. left is topLeft, top is topRight, right is 44 | * rightBottom and bottom is leftBottom. 45 | * @param viewPortGLBounds the bounds of the GL viewport in GL scalar units. 46 | * @param viewPortPxSize the size of the view port in pixels. 47 | * @param z the z coordinate for the z-plane geometry. 48 | * @return an object with the resulting geometry. 49 | */ 50 | @NonNull 51 | public GeometryArrays generateVertexData(@NonNull RectF radii, @NonNull RectF viewPortGLBounds, 52 | @NonNull Point viewPortPxSize, float z) { 53 | final float x0 = viewPortGLBounds.left; 54 | final float x1 = viewPortGLBounds.right; 55 | final float y0 = viewPortGLBounds.bottom; 56 | final float y1 = viewPortGLBounds.top; 57 | 58 | final float leftTopRadius = radii.left; 59 | final float rightTopRadius = radii.top; 60 | final float rightBottomRadius = radii.right; 61 | final float leftBottomRadius = radii.bottom; 62 | 63 | mTopLeftRadius[0] = leftTopRadius / viewPortPxSize.x * viewPortGLBounds.width(); 64 | mTopLeftRadius[1] = leftTopRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 65 | mTopRightRadius[0] = rightTopRadius / viewPortPxSize.x * viewPortGLBounds.width(); 66 | mTopRightRadius[1] = rightTopRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 67 | mBottomRightRadius[0] = rightBottomRadius / viewPortPxSize.x * viewPortGLBounds.width(); 68 | mBottomRightRadius[1] = rightBottomRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 69 | mBottomLeftRadius[0] = leftBottomRadius / viewPortPxSize.x * viewPortGLBounds.width(); 70 | mBottomLeftRadius[1] = leftBottomRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 71 | 72 | mLeftTop[0] = x0; 73 | mLeftTop[1] = y1 - mTopLeftRadius[1]; 74 | mLeftBottom[0] = x0; 75 | mLeftBottom[1] = y0 + mBottomLeftRadius[1]; 76 | mTopLeft[0] = x0 + mTopLeftRadius[0]; 77 | mTopLeft[1] = y1; 78 | mTopRight[0] = x1 - mTopRightRadius[0]; 79 | mTopRight[1] = y1; 80 | mRightTop[0] = x1; 81 | mRightTop[1] = y1 - mTopRightRadius[1]; 82 | mRightBottom[0] = x1; 83 | mRightBottom[1] = y0 + mBottomRightRadius[1]; 84 | mBottomLeft[0] = x0 + mBottomLeftRadius[0]; 85 | mBottomLeft[1] = y0; 86 | mBottomRight[0] = x1 - mBottomRightRadius[0]; 87 | mBottomRight[1] = y0; 88 | 89 | mInnerTopLeft[0] = mTopLeft[0]; 90 | mInnerTopLeft[1] = mLeftTop[1]; 91 | mInnerTopRight[0] = mTopRight[0]; 92 | mInnerTopRight[1] = mRightTop[1]; 93 | mInnerBottomLeft[0] = mBottomLeft[0]; 94 | mInnerBottomLeft[1] = mLeftBottom[1]; 95 | mInnerBottomRight[0] = mBottomRight[0]; 96 | mInnerBottomRight[1] = mRightBottom[1]; 97 | 98 | // Each vertex has 5 floats (xyz + uv) 99 | // 5 squares (each has 4 vertices) 100 | // 4 rounded corners (each has X triangles, each triangle has 3 vertices) 101 | final int trianglesPerCorner = 6; 102 | final int floatsPerRoundedCorner = (trianglesPerCorner + 2) * 5; 103 | final int floatsPerSquare = 4 * 5; 104 | final int shortsPerTriangle = 3; 105 | final int shortsPerSquare = 2 * shortsPerTriangle; 106 | final int verticesSize = 5 * floatsPerSquare + 4 * floatsPerRoundedCorner; 107 | final int indicesSize = 5 * shortsPerSquare + 4 * trianglesPerCorner * shortsPerTriangle; 108 | final float[] vertices = new float[verticesSize]; 109 | final short[] indices = new short[indicesSize]; 110 | final GeometryArrays geoArrays = new GeometryArrays(vertices, indices); 111 | 112 | // Inner center rect 113 | addRect(geoArrays, new float[][]{ 114 | mInnerTopLeft, mInnerTopRight, mInnerBottomLeft, mInnerBottomRight}, 115 | viewPortGLBounds, z); 116 | geoArrays.verticesOffset += floatsPerSquare; 117 | geoArrays.indicesOffset += shortsPerSquare; 118 | 119 | // Left rect 120 | addRect(geoArrays, new float[][]{ 121 | mLeftTop, mInnerTopLeft, mLeftBottom, mInnerBottomLeft}, 122 | viewPortGLBounds, z); 123 | geoArrays.verticesOffset += floatsPerSquare; 124 | geoArrays.indicesOffset += shortsPerSquare; 125 | 126 | // Right rect 127 | addRect(geoArrays, new float[][]{ 128 | mInnerTopRight, mRightTop, mInnerBottomRight, mRightBottom}, 129 | viewPortGLBounds, z); 130 | geoArrays.verticesOffset += floatsPerSquare; 131 | geoArrays.indicesOffset += shortsPerSquare; 132 | 133 | // Top rect 134 | addRect(geoArrays, new float[][]{ 135 | mTopLeft, mInnerTopLeft, mTopRight, mInnerTopRight}, 136 | viewPortGLBounds, z); 137 | geoArrays.verticesOffset += floatsPerSquare; 138 | geoArrays.indicesOffset += shortsPerSquare; 139 | 140 | // Bottom rect 141 | addRect(geoArrays, new float[][]{ 142 | mInnerBottomLeft, mBottomLeft, mInnerBottomRight, mBottomRight}, 143 | viewPortGLBounds, z); 144 | geoArrays.verticesOffset += floatsPerSquare; 145 | geoArrays.indicesOffset += shortsPerSquare; 146 | 147 | // These assume uniform corners (i.e. same radius on both axis) 148 | // Top left corner 149 | addRoundedCorner(geoArrays, mInnerTopLeft, mTopLeftRadius, (float) Math.PI, 150 | (float) (Math.PI / 2.0), trianglesPerCorner, viewPortGLBounds, z); 151 | geoArrays.verticesOffset += floatsPerRoundedCorner; 152 | geoArrays.indicesOffset += trianglesPerCorner * shortsPerTriangle; 153 | 154 | // Top right corner 155 | addRoundedCorner(geoArrays, mInnerTopRight, mTopRightRadius, (float) (Math.PI / 2), 0f, 156 | trianglesPerCorner, viewPortGLBounds, z); 157 | geoArrays.verticesOffset += floatsPerRoundedCorner; 158 | geoArrays.indicesOffset += trianglesPerCorner * shortsPerTriangle; 159 | 160 | // Bottom right corner 161 | addRoundedCorner(geoArrays, mInnerBottomRight, mBottomRightRadius, 162 | (float) (Math.PI * 3.0 / 2.0), (float) Math.PI * 2, trianglesPerCorner, 163 | viewPortGLBounds, z); 164 | geoArrays.verticesOffset += floatsPerRoundedCorner; 165 | geoArrays.indicesOffset += trianglesPerCorner * shortsPerTriangle; 166 | 167 | // Bottom left corner 168 | addRoundedCorner(geoArrays, mInnerBottomLeft, mBottomLeftRadius, (float) Math.PI, 169 | (float) (Math.PI * 3.0 / 2.0), trianglesPerCorner, viewPortGLBounds, z); 170 | 171 | return new GeometryArrays(vertices, indices); 172 | } 173 | 174 | /** 175 | * Adds the vertices of a rectangle defined by 4 corner points. The array of vertices passed 176 | * in must have the required length to add the geometry points (5 floats for each vertex). Also 177 | * the coordinates of the rect corners should already be in the view port space. 178 | * 179 | * @param geoArrays an object containing the vertex and index data arrays and their current 180 | * offsets. 181 | * @param rectPoints an array of corner points defining the rectangle. index 0 is the x 182 | * coordinate and index 1 the y coordinate. 183 | * @param viewPort the bounds of the current GL viewport, this is used to calculate the texture 184 | * mapping. 185 | * @param z the z coordinate. 186 | */ 187 | private void addRect(@NonNull GeometryArrays geoArrays, 188 | @NonNull float[][] rectPoints, 189 | @NonNull RectF viewPort, 190 | float z) { 191 | final float[] vertices = geoArrays.triangleVertices; 192 | final short[] indices = geoArrays.triangleIndices; 193 | final int indicesOffset = geoArrays.indicesOffset; 194 | final int verticesOffset = geoArrays.verticesOffset; 195 | int rectPointIdx = 0; 196 | for (final float[] rectPoint : rectPoints) { 197 | // 5 values [xyzuv] per vertex 198 | final int currentVertexOffset = verticesOffset + rectPointIdx * 5; 199 | 200 | // XYZ (vertex space coordinates 201 | vertices[currentVertexOffset + 0] = rectPoint[0]; 202 | vertices[currentVertexOffset + 1] = rectPoint[1]; 203 | vertices[currentVertexOffset + 2] = z; 204 | 205 | // UV (texture mapping) 206 | vertices[currentVertexOffset + 3] = (rectPoint[0] - viewPort.left) / viewPort.width(); 207 | vertices[currentVertexOffset + 4] = (rectPoint[1] - viewPort.bottom) / -viewPort.height(); 208 | 209 | rectPointIdx++; 210 | } 211 | 212 | // Index our triangles -- tell where each triangle vertex is 213 | final int initialIdx = verticesOffset / 5; 214 | indices[indicesOffset + 0] = (short) (initialIdx); 215 | indices[indicesOffset + 1] = (short) (initialIdx + 1); 216 | indices[indicesOffset + 2] = (short) (initialIdx + 2); 217 | indices[indicesOffset + 3] = (short) (initialIdx + 1); 218 | indices[indicesOffset + 4] = (short) (initialIdx + 2); 219 | indices[indicesOffset + 5] = (short) (initialIdx + 3); 220 | } 221 | 222 | /** 223 | * Adds the vertices of a number of triangles to form a rounded corner. The triangles start at 224 | * some center point and will sweep from a given initial angle up to a final one. The size of 225 | * the triangles is defined by the radius. 226 | * 227 | * The array of vertices passed in must have the required length to add the geometry points 228 | * (5 floats for each vertex). Also the coordinates of the rect corners should already be in 229 | * the view port space. 230 | * 231 | * @param geoArrays an object containing the vertex and index data arrays and their current 232 | * offsets. 233 | * @param center the center point where all triangles will start. 234 | * @param radius the desired radius in the x and y axis, in viewport dimensions. 235 | * @param rads0 the initial angle. 236 | * @param rads1 the final angle. 237 | * @param triangles the amount of triangles to create. 238 | * @param viewPort the bounds of the current GL viewport, this is used to calculate the texture 239 | * mapping. 240 | * @param z the z coordinate. 241 | */ 242 | private void addRoundedCorner(@NonNull GeometryArrays geoArrays, 243 | @NonNull float[] center, 244 | float[] radius, 245 | float rads0, 246 | float rads1, 247 | int triangles, 248 | @NonNull RectF viewPort, 249 | float z) { 250 | final float[] vertices = geoArrays.triangleVertices; 251 | final short[] indices = geoArrays.triangleIndices; 252 | final int verticesOffset = geoArrays.verticesOffset; 253 | final int indicesOffset = geoArrays.indicesOffset; 254 | for (int i = 0; i < triangles; i++) { 255 | // final int currentOffset = verticesOffset + i * 15 /* each triangle is 3 * xyzuv */; 256 | final int currentOffset = verticesOffset + i * 5 + (i > 0 ? 2 * 5 : 0); 257 | final float rads = rads0 + (rads1 - rads0) * (i / (float) triangles); 258 | final float radsNext = rads0 + (rads1 - rads0) * ((i + 1) / (float) triangles); 259 | final int triangleEdge2Offset; 260 | 261 | if (i == 0) { 262 | // XYZUV - center point 263 | vertices[currentOffset + 0] = center[0]; 264 | vertices[currentOffset + 1] = center[1]; 265 | vertices[currentOffset + 2] = z; 266 | vertices[currentOffset + 3] = 267 | (vertices[currentOffset + 0] - viewPort.left) / viewPort.width(); 268 | vertices[currentOffset + 4] = 269 | (vertices[currentOffset + 1] - viewPort.bottom) / -viewPort.height(); 270 | 271 | // XYZUV - triangle edge 1 272 | vertices[currentOffset + 5] = center[0] + radius[0] * (float) Math.cos(rads); 273 | vertices[currentOffset + 6] = center[1] + radius[1] * (float) Math.sin(rads); 274 | vertices[currentOffset + 7] = z; 275 | vertices[currentOffset + 8] = 276 | (vertices[currentOffset + 5] - viewPort.left) / viewPort.width(); 277 | vertices[currentOffset + 9] = 278 | (vertices[currentOffset + 6] - viewPort.bottom) / -viewPort.height(); 279 | 280 | triangleEdge2Offset = 10; 281 | } else { 282 | triangleEdge2Offset = 0; 283 | } 284 | 285 | // XYZUV - triangle edge 2 286 | final int edge2Offset = currentOffset + triangleEdge2Offset; 287 | vertices[edge2Offset + 0] = center[0] + radius[0] * (float) Math.cos(radsNext); 288 | vertices[edge2Offset + 1] = center[1] + radius[1] * (float) Math.sin(radsNext); 289 | vertices[edge2Offset + 2] = z; 290 | vertices[edge2Offset + 3] = 291 | (vertices[edge2Offset + 0] - viewPort.left) / viewPort.width(); 292 | vertices[edge2Offset + 4] = 293 | (vertices[edge2Offset + 1] - viewPort.bottom) / -viewPort.height(); 294 | 295 | // Index our triangles -- tell where each triangle vertex is 296 | final int initialIdx = verticesOffset / 5; 297 | indices[indicesOffset + i * 3 + 0] = (short) (initialIdx); 298 | indices[indicesOffset + i * 3 + 1] = (short) (initialIdx + i + 1); 299 | indices[indicesOffset + i * 3 + 2] = (short) (initialIdx + i + 2); 300 | } 301 | } 302 | 303 | public static class GeometryArrays { 304 | public float[] triangleVertices; 305 | public short[] triangleIndices; 306 | public int verticesOffset = 0; 307 | public int indicesOffset = 0; 308 | 309 | public GeometryArrays(@NonNull float[] vertices, @NonNull short[] indices) { 310 | triangleVertices = vertices; 311 | triangleIndices = indices; 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/videochatheads/views/MultiSampleEGLConfigChooser.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.videochatheads.views; 2 | 3 | import android.opengl.GLSurfaceView; 4 | import android.util.Log; 5 | 6 | import javax.microedition.khronos.egl.EGL10; 7 | import javax.microedition.khronos.egl.EGLConfig; 8 | import javax.microedition.khronos.egl.EGLDisplay; 9 | 10 | /** 11 | * EGL configuration Chooser slightly adapted from: 12 | * 13 | * https://code.google.com/p/gdc2011-android-opengl/source/browse/trunk/src/com/example/ 14 | * gdc11/MultisampleConfigChooser.java 15 | * 16 | * This class shows how to use multisampling. To use this, call 17 | * myGLSurfaceView.setEGLConfigChooser(new MultisampleConfigChooser()); before calling 18 | * setRenderer(). Multisampling will probably slow down your app -- measure performance carefully 19 | * and decide if the vastly improved visual quality is worth the cost. 20 | */ 21 | public class MultiSampleEGLConfigChooser implements GLSurfaceView.EGLConfigChooser { 22 | static private final String TAG = MultiSampleEGLConfigChooser.class.getSimpleName(); 23 | @Override 24 | public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { 25 | mValue = new int[1]; 26 | 27 | // Try to find a normal multisample configuration first. 28 | int[] configSpec = { 29 | EGL10.EGL_RED_SIZE, 8, 30 | EGL10.EGL_GREEN_SIZE, 8, 31 | EGL10.EGL_BLUE_SIZE, 8, 32 | EGL10.EGL_ALPHA_SIZE, 8, 33 | EGL10.EGL_DEPTH_SIZE, 16, 34 | // Requires that setEGLContextClientVersion(2) is called on the view. 35 | EGL10.EGL_RENDERABLE_TYPE, 4 /* EGL_OPENGL_ES2_BIT */, 36 | EGL10.EGL_SAMPLE_BUFFERS, 1 /* true */, 37 | EGL10.EGL_SAMPLES, 4, 38 | EGL10.EGL_NONE 39 | }; 40 | 41 | int numConfigs = mValue[0]; 42 | 43 | if (numConfigs <= 0) { 44 | // No normal multisampling config was found. Try to create a 45 | // coverage multisampling configuration, for the nVidia Tegra2. 46 | // See the EGL_NV_coverage_sample documentation. 47 | 48 | final int EGL_COVERAGE_BUFFERS_NV = 0x30E0; 49 | final int EGL_COVERAGE_SAMPLES_NV = 0x30E1; 50 | 51 | configSpec = new int[]{ 52 | EGL10.EGL_RED_SIZE, 8, 53 | EGL10.EGL_GREEN_SIZE, 8, 54 | EGL10.EGL_BLUE_SIZE, 8, 55 | EGL10.EGL_ALPHA_SIZE, 8, 56 | EGL10.EGL_DEPTH_SIZE, 16, 57 | EGL10.EGL_RENDERABLE_TYPE, 4 /* EGL_OPENGL_ES2_BIT */, 58 | EGL_COVERAGE_BUFFERS_NV, 1 /* true */, 59 | EGL_COVERAGE_SAMPLES_NV, 2, // always 5 in practice on tegra 2 60 | EGL10.EGL_NONE 61 | }; 62 | 63 | numConfigs = mValue[0]; 64 | 65 | if (numConfigs <= 0) { 66 | // Give up, try without multisampling. 67 | configSpec = new int[]{ 68 | EGL10.EGL_RED_SIZE, 8, 69 | EGL10.EGL_GREEN_SIZE, 8, 70 | EGL10.EGL_BLUE_SIZE, 8, 71 | EGL10.EGL_ALPHA_SIZE, 8, 72 | EGL10.EGL_DEPTH_SIZE, 16, 73 | EGL10.EGL_RENDERABLE_TYPE, 4 /* EGL_OPENGL_ES2_BIT */, 74 | EGL10.EGL_NONE 75 | }; 76 | 77 | if (!egl.eglChooseConfig(display, configSpec, null, 0, mValue)) { 78 | throw new IllegalArgumentException("3rd eglChooseConfig failed"); 79 | } 80 | numConfigs = mValue[0]; 81 | 82 | if (numConfigs <= 0) { 83 | throw new IllegalArgumentException("No configs match configSpec"); 84 | } 85 | } else { 86 | mUsesCoverageAa = true; 87 | } 88 | } 89 | 90 | // Get all matching configurations. 91 | EGLConfig[] configs = new EGLConfig[numConfigs]; 92 | if (!egl.eglChooseConfig(display, configSpec, configs, numConfigs, mValue)) { 93 | throw new IllegalArgumentException("data eglChooseConfig failed"); 94 | } 95 | 96 | // CAUTION! eglChooseConfigs returns configs with higher bit depth 97 | // first: Even if you asked for rgb565 configurations, rgb888 98 | // configurations are considered to be "better" and returned first. 99 | // You need to explicitly filter the data returned by eglChooseConfig! 100 | // 101 | // In this case we asked for ARGB8888 so we should be good taking the 102 | // first result, but we'll still filter explicitly the one we want 103 | // just to be safe. 104 | int index = -1; 105 | for (int i = 0; i < configs.length; ++i) { 106 | if (findConfigAttrib(egl, display, configs[i], EGL10.EGL_ALPHA_SIZE, 0) == 8) { 107 | index = i; 108 | break; 109 | } 110 | } 111 | if (index == -1) { 112 | Log.w(TAG, "Did not find sane config, using first"); 113 | } 114 | EGLConfig config = configs.length > 0 ? configs[index] : null; 115 | if (config == null) { 116 | throw new IllegalArgumentException("No config chosen"); 117 | } 118 | return config; 119 | } 120 | 121 | private int findConfigAttrib(EGL10 egl, EGLDisplay display, 122 | EGLConfig config, int attribute, int defaultValue) { 123 | if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { 124 | return mValue[0]; 125 | } 126 | return defaultValue; 127 | } 128 | 129 | public boolean usesCoverageAa() { 130 | return mUsesCoverageAa; 131 | } 132 | 133 | private int[] mValue; 134 | private boolean mUsesCoverageAa; 135 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/videochatheads/views/VideoSurfaceView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 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 | 17 | package com.takeoffandroid.videochatheads.views; 18 | 19 | import android.content.Context; 20 | import android.graphics.PixelFormat; 21 | import android.graphics.Point; 22 | import android.graphics.RectF; 23 | import android.graphics.SurfaceTexture; 24 | import android.media.MediaPlayer; 25 | import android.opengl.GLES20; 26 | import android.opengl.GLSurfaceView; 27 | import android.opengl.Matrix; 28 | import android.support.annotation.NonNull; 29 | import android.support.annotation.Nullable; 30 | import android.util.AttributeSet; 31 | import android.util.Log; 32 | import android.view.Surface; 33 | 34 | import java.io.IOException; 35 | import java.nio.ByteBuffer; 36 | import java.nio.ByteOrder; 37 | import java.nio.FloatBuffer; 38 | import java.nio.ShortBuffer; 39 | 40 | import javax.microedition.khronos.egl.EGLConfig; 41 | import javax.microedition.khronos.opengles.GL10; 42 | 43 | /** 44 | * This class has been adapted from 45 | * 46 | * https://code.google.com/p/android-source-browsing/source/browse/tests/tests/media/src/ 47 | * android/media/cts/VideoSurfaceView.java?repo=platform--cts 48 | * 49 | * {@link GLSurfaceView} subclass that displays video on the screen with the ability 50 | * to round its corners. 51 | * 52 | * It creates a GL texture and from that generates a {@link SurfaceTexture} and an 53 | * associated {@link Surface} which it binds to a given 54 | * {@link MediaPlayer}. The {@link MediaPlayer} will draw the decoded 55 | * video frames directly on to the GL texture which afterwards is rendered on the screen mapped to 56 | * a given (rounded corner) geometry. 57 | * 58 | * To set adjust the rounded corners use {@link #setCornerRadius(float, float, float, float)}. 59 | * 60 | */ 61 | public class VideoSurfaceView extends GLSurfaceView { 62 | private static final String TAG = "VideoSurfaceView"; 63 | private static final boolean USE_MULTI_SAMPLING = true; 64 | 65 | VideoRenderer mRenderer; 66 | MediaPlayer mMediaPlayer = null; 67 | MultiSampleEGLConfigChooser mMultiSamplingConfigChooser; 68 | 69 | public VideoSurfaceView(Context context) { 70 | super(context); 71 | init(new VideoRenderer(this)); 72 | } 73 | 74 | public VideoSurfaceView(Context context, AttributeSet attrs) { 75 | super(context, attrs); 76 | init(new VideoRenderer(this)); 77 | } 78 | 79 | VideoSurfaceView(Context context, @NonNull VideoRenderer videoRender) { 80 | super(context); 81 | init(videoRender); 82 | } 83 | 84 | private void init(@NonNull VideoRenderer videoRender) { 85 | setEGLContextClientVersion(2); 86 | 87 | setupEGLConfig(true, USE_MULTI_SAMPLING); 88 | if (USE_MULTI_SAMPLING && mMultiSamplingConfigChooser != null) { 89 | videoRender.setUsesCoverageAa(mMultiSamplingConfigChooser.usesCoverageAa()); 90 | } 91 | mRenderer = videoRender; 92 | setRenderer(mRenderer); 93 | setRenderMode(RENDERMODE_WHEN_DIRTY); 94 | } 95 | 96 | /** 97 | * Make sure the {@link android.view.SurfaceHolder} pixel format matches your EGL configuration. 98 | * 99 | * @param translucent true if the view should show views below if parts of the view area are 100 | * transparent. Has performance implications. 101 | * @param multisampling true if the GL Surface should perform multi-sampling. This avoids hard 102 | * edges on the geometry. Has performance implications. 103 | */ 104 | private void setupEGLConfig(boolean translucent, boolean multisampling) { 105 | if (translucent) { 106 | setZOrderOnTop(true); 107 | if (multisampling) { 108 | mMultiSamplingConfigChooser = new MultiSampleEGLConfigChooser(); 109 | setEGLConfigChooser(mMultiSamplingConfigChooser); 110 | } else { 111 | setEGLConfigChooser(8, 8, 8, 8, 16, 0); 112 | } 113 | this.getHolder().setFormat(PixelFormat.RGBA_8888); 114 | } else { 115 | if (multisampling) { 116 | mMultiSamplingConfigChooser = new MultiSampleEGLConfigChooser(); 117 | setEGLConfigChooser(mMultiSamplingConfigChooser); 118 | } else { 119 | setEGLConfigChooser(5, 6, 5, 0, 16, 0); 120 | } 121 | this.getHolder().setFormat(PixelFormat.RGB_565); 122 | } 123 | } 124 | 125 | public void setCornerRadius(float radius) { 126 | setCornerRadius(radius, radius, radius, radius); 127 | } 128 | 129 | public void setCornerRadius(float topLeft, float topRight, float bottomRight, 130 | float bottomLeft) { 131 | mRenderer.setCornerRadius(topLeft, topRight, bottomRight, bottomLeft); 132 | } 133 | 134 | // TODO 135 | public void setVideoAspectRatio(float aspectRatio) { 136 | mRenderer.setVideoAspectRatio(aspectRatio); 137 | } 138 | 139 | @Override 140 | public void onResume() { 141 | queueEvent(new Runnable(){ 142 | public void run() { 143 | mRenderer.setMediaPlayer(mMediaPlayer); 144 | }}); 145 | 146 | super.onResume(); 147 | } 148 | 149 | public void setMediaPlayer(@Nullable MediaPlayer mediaPlayer) { 150 | mMediaPlayer = mediaPlayer; 151 | if (mRenderer != null) { 152 | mRenderer.setMediaPlayer(mediaPlayer); 153 | } 154 | } 155 | 156 | private static class VideoRenderer 157 | implements Renderer, SurfaceTexture.OnFrameAvailableListener { 158 | private static String TAG = "VideoRender"; 159 | 160 | private static final int FLOAT_SIZE_BYTES = 4; 161 | private static final int SHORT_SIZE_BYTES = 2; 162 | private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; 163 | private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; 164 | private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; 165 | 166 | private final boolean USE_DRAW_ELEMENTS = true; 167 | 168 | private final String mVertexShader = 169 | "uniform mat4 uMVPMatrix;\n" + 170 | "uniform mat4 uSTMatrix;\n" + 171 | "attribute vec4 aPosition;\n" + 172 | "attribute vec4 aTextureCoord;\n" + 173 | "varying vec2 vTextureCoord;\n" + 174 | "void main() {\n" + 175 | " gl_Position = uMVPMatrix * aPosition;\n" + 176 | " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + 177 | "}\n"; 178 | 179 | private final String mFragmentShader = 180 | "#extension GL_OES_EGL_image_external : require\n" + 181 | "precision mediump float;\n" + 182 | "varying vec2 vTextureCoord;\n" + 183 | "uniform samplerExternalOES sTexture;\n" + 184 | "void main() {\n" + 185 | " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + 186 | "}\n"; 187 | 188 | private float[] mMVPMatrix = new float[16]; 189 | private float[] mSTMatrix = new float[16]; 190 | 191 | private int mProgram; 192 | private int mTextureID; 193 | private int muMVPMatrixHandle; 194 | private int muSTMatrixHandle; 195 | private int maPositionHandle; 196 | private int maTextureHandle; 197 | 198 | private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65; 199 | 200 | private final GLSurfaceView mGLSurfaceView; 201 | private MediaPlayer mMediaPlayer; 202 | private SurfaceTexture mSurfaceTexture; 203 | private boolean mUpdateSurface = false; 204 | 205 | private float[] mTriangleVerticesData; 206 | private short[] mTriangleIndicesData; 207 | private FloatBuffer mTriangleVertices; 208 | private ShortBuffer mTriangleIndices; 209 | private RectF mRoundRadius = new RectF(); 210 | private GLRoundedGeometry mRoundedGeometry; 211 | private final Point mViewPortSize = new Point(); 212 | private final RectF mViewPortGLBounds; 213 | private boolean mUsesCoverageAa = false; 214 | 215 | public VideoRenderer(@NonNull GLSurfaceView view) { 216 | this(view, new GLRoundedGeometry(), new RectF(-1, 1, 1, -1)); 217 | } 218 | 219 | public VideoRenderer(@NonNull GLSurfaceView view, 220 | @NonNull GLRoundedGeometry roundedGeometry, 221 | @NonNull RectF viewPortGLBounds) { 222 | mGLSurfaceView = view; 223 | mRoundedGeometry = roundedGeometry; 224 | mViewPortGLBounds = viewPortGLBounds; 225 | mViewPortSize.set(1, 1); // init this with a non-zero size 226 | 227 | Matrix.setIdentityM(mSTMatrix, 0); 228 | } 229 | 230 | public void setUsesCoverageAa(boolean usesCoverageAa) { 231 | mUsesCoverageAa = usesCoverageAa; 232 | } 233 | 234 | public void setCornerRadius(float topLeft, float topRight, float bottomRight, 235 | float bottomLeft) { 236 | mRoundRadius.left = topLeft; 237 | mRoundRadius.top = topRight; 238 | mRoundRadius.right = bottomRight; 239 | mRoundRadius.bottom = bottomLeft; 240 | if (mViewPortSize.x > 1) { 241 | updateVertexData(); 242 | } 243 | } 244 | 245 | private void updateVertexData() { 246 | final GLRoundedGeometry.GeometryArrays arrays = 247 | mRoundedGeometry.generateVertexData( 248 | mRoundRadius, 249 | mViewPortGLBounds, 250 | mViewPortSize); 251 | mTriangleVerticesData = arrays.triangleVertices; 252 | mTriangleIndicesData = arrays.triangleIndices; 253 | if (mTriangleVertices != null) { 254 | mTriangleVertices.clear(); 255 | } else { 256 | mTriangleVertices = ByteBuffer.allocateDirect( 257 | mTriangleVerticesData.length * FLOAT_SIZE_BYTES) 258 | .order(ByteOrder.nativeOrder()).asFloatBuffer(); 259 | } 260 | if (mTriangleIndices != null) { 261 | mTriangleIndices.clear(); 262 | } else { 263 | mTriangleIndices = ByteBuffer.allocateDirect( 264 | mTriangleIndicesData.length * SHORT_SIZE_BYTES) 265 | .order(ByteOrder.nativeOrder()).asShortBuffer(); 266 | } 267 | mTriangleVertices.put(mTriangleVerticesData).position(0); 268 | mTriangleIndices.put(mTriangleIndicesData).position(0); 269 | } 270 | 271 | public void setMediaPlayer(MediaPlayer player) { 272 | mMediaPlayer = player; 273 | if (mSurfaceTexture != null) { 274 | Surface surface = new Surface(mSurfaceTexture); 275 | mMediaPlayer.setSurface(surface); 276 | surface.release(); 277 | 278 | try { 279 | mMediaPlayer.prepare(); 280 | } catch (IOException t) { 281 | Log.e(TAG, "media player prepare failed"); 282 | } 283 | } 284 | } 285 | 286 | public void onDrawFrame(GL10 glUnused) { 287 | synchronized(this) { 288 | if (mUpdateSurface) { 289 | mSurfaceTexture.updateTexImage(); 290 | mSurfaceTexture.getTransformMatrix(mSTMatrix); 291 | mUpdateSurface = false; 292 | } 293 | } 294 | 295 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 296 | int clearMask = GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT; 297 | if (mUsesCoverageAa) { // Tegra weirdness 298 | final int GL_COVERAGE_BUFFER_BIT_NV = 0x8000; 299 | clearMask |= GL_COVERAGE_BUFFER_BIT_NV; 300 | } 301 | GLES20.glClear(clearMask); 302 | 303 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, 304 | GLES20.GL_CLAMP_TO_EDGE); 305 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, 306 | GLES20.GL_CLAMP_TO_EDGE); 307 | 308 | GLES20.glUseProgram(mProgram); 309 | checkGlError("glUseProgram"); 310 | 311 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 312 | GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID); 313 | 314 | mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); 315 | GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 316 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); 317 | checkGlError("glVertexAttribPointer maPosition"); 318 | GLES20.glEnableVertexAttribArray(maPositionHandle); 319 | checkGlError("glEnableVertexAttribArray maPositionHandle"); 320 | 321 | mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); 322 | GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false, 323 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); 324 | checkGlError("glVertexAttribPointer maTextureHandle"); 325 | GLES20.glEnableVertexAttribArray(maTextureHandle); 326 | checkGlError("glEnableVertexAttribArray maTextureHandle"); 327 | 328 | Matrix.setIdentityM(mMVPMatrix, 0); 329 | Matrix.scaleM(mMVPMatrix, 0, 1f, 1f, 1f); 330 | GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); 331 | GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); 332 | 333 | // Alternatively we can use 334 | // 335 | // GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mTriangleVerticesData.length / 5); 336 | // 337 | // but with the current geometry setup it ends up drawing a lot of 'degenerate' 338 | // triangles which represents more work for our shaders, especially the fragment one. 339 | GLES20.glDrawElements(GLES20.GL_TRIANGLES, mTriangleIndicesData.length, 340 | GL10.GL_UNSIGNED_SHORT, mTriangleIndices); 341 | 342 | checkGlError("glDrawElements"); 343 | GLES20.glFinish(); 344 | } 345 | 346 | public void onSurfaceChanged(GL10 glUnused, int width, int height) { 347 | GLES20.glViewport(0, 0, width, height); 348 | mViewPortSize.set(width, height); 349 | updateVertexData(); 350 | } 351 | 352 | public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { 353 | mProgram = createProgram(mVertexShader, mFragmentShader); 354 | if (mProgram == 0) { 355 | return; 356 | } 357 | maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); 358 | checkGlError("glGetAttribLocation aPosition"); 359 | if (maPositionHandle == -1) { 360 | throw new RuntimeException("Could not get attrib location for aPosition"); 361 | } 362 | maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); 363 | checkGlError("glGetAttribLocation aTextureCoord"); 364 | if (maTextureHandle == -1) { 365 | throw new RuntimeException("Could not get attrib location for aTextureCoord"); 366 | } 367 | 368 | muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); 369 | checkGlError("glGetUniformLocation uMVPMatrix"); 370 | if (muMVPMatrixHandle == -1) { 371 | throw new RuntimeException("Could not get attrib location for uMVPMatrix"); 372 | } 373 | 374 | muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); 375 | checkGlError("glGetUniformLocation uSTMatrix"); 376 | if (muSTMatrixHandle == -1) { 377 | throw new RuntimeException("Could not get attrib location for uSTMatrix"); 378 | } 379 | 380 | int[] textures = new int[1]; 381 | GLES20.glGenTextures(1, textures, 0); 382 | 383 | mTextureID = textures[0]; 384 | GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID); 385 | checkGlError("glBindTexture mTextureID"); 386 | 387 | GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, 388 | GLES20.GL_LINEAR); 389 | GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, 390 | GLES20.GL_LINEAR); 391 | 392 | /* 393 | * Create the SurfaceTexture that will feed this textureID, 394 | * and pass it to the MediaPlayer 395 | */ 396 | mSurfaceTexture = new SurfaceTexture(mTextureID); 397 | mSurfaceTexture.setOnFrameAvailableListener(this); 398 | 399 | if (mMediaPlayer != null) { 400 | Surface surface = new Surface(mSurfaceTexture); 401 | mMediaPlayer.setSurface(surface); 402 | surface.release(); 403 | try { 404 | mMediaPlayer.prepare(); 405 | } catch (IOException t) { 406 | Log.e(TAG, "media player prepare failed"); 407 | } 408 | } 409 | 410 | synchronized(this) { 411 | mUpdateSurface = false; 412 | } 413 | } 414 | 415 | synchronized public void onFrameAvailable(SurfaceTexture surface) { 416 | mUpdateSurface = true; 417 | mGLSurfaceView.requestRender(); 418 | } 419 | 420 | private int loadShader(int shaderType, String source) { 421 | int shader = GLES20.glCreateShader(shaderType); 422 | if (shader != 0) { 423 | GLES20.glShaderSource(shader, source); 424 | GLES20.glCompileShader(shader); 425 | int[] compiled = new int[1]; 426 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 427 | if (compiled[0] == 0) { 428 | Log.e(TAG, "Could not compile shader " + shaderType + ":"); 429 | Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); 430 | GLES20.glDeleteShader(shader); 431 | shader = 0; 432 | } 433 | } 434 | return shader; 435 | } 436 | 437 | private int createProgram(String vertexSource, String fragmentSource) { 438 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 439 | if (vertexShader == 0) { 440 | return 0; 441 | } 442 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 443 | if (pixelShader == 0) { 444 | return 0; 445 | } 446 | 447 | int program = GLES20.glCreateProgram(); 448 | if (program != 0) { 449 | GLES20.glAttachShader(program, vertexShader); 450 | checkGlError("glAttachShader"); 451 | GLES20.glAttachShader(program, pixelShader); 452 | checkGlError("glAttachShader"); 453 | GLES20.glLinkProgram(program); 454 | int[] linkStatus = new int[1]; 455 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 456 | if (linkStatus[0] != GLES20.GL_TRUE) { 457 | Log.e(TAG, "Could not link program: "); 458 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 459 | GLES20.glDeleteProgram(program); 460 | program = 0; 461 | } 462 | } 463 | return program; 464 | } 465 | 466 | private void checkGlError(String op) { 467 | int error; 468 | while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 469 | Log.e(TAG, op + ": glError " + error); 470 | throw new RuntimeException(op + ": glError " + error); 471 | } 472 | } 473 | 474 | public void setVideoAspectRatio(float aspectRatio) { 475 | // TODO 476 | } 477 | } 478 | 479 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_shade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/drawable/background_shade.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/drawable_circle_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/drawable/ic_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_layout_chat_heads.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_layout_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/raw/sample_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/VideoChatHeads/fdb4a4310e20aa8ba1bc569967dac81698f07783/app/src/main/res/raw/sample_video.mp4 -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 39dip 6 | 233dp 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Facebook-VideoChatHeads 3 | Settings 4 | Press play button and throw the paper into recycle bin. 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |