@byteplus/react-native-live-pull 1.1.2-rc.2 → 1.1.3-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowService.java +719 -40
- package/lib/commonjs/index.js +0 -32
- package/lib/module/index.js +0 -32
- package/lib/typescript/codegen/pack/keytype.d.ts +0 -12
- package/lib/typescript/core/keytype.d.ts +16 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.volcengine.velive.rn.pull.pictureInpicture;
|
|
2
2
|
|
|
3
|
+
import android.animation.ValueAnimator;
|
|
3
4
|
import android.app.Service;
|
|
4
5
|
import android.content.BroadcastReceiver;
|
|
5
6
|
import android.content.Context;
|
|
@@ -24,14 +25,19 @@ import androidx.annotation.Nullable;
|
|
|
24
25
|
import com.volcengine.velive.rn.pull.R;
|
|
25
26
|
|
|
26
27
|
public class FloatingWindowService extends Service {
|
|
28
|
+
private static final int MARGIN_TOP = 40;
|
|
29
|
+
private static final int MARGIN_BOTTOM = 40;
|
|
30
|
+
private static final int MARGIN_LEFT = 20;
|
|
31
|
+
private static final int MARGIN_RIGHT = 20;
|
|
27
32
|
private static final String TAG = FloatingWindowService.class.getSimpleName();
|
|
28
|
-
private static final int LONGER_SIDE_MAX_LEN = 1000;
|
|
29
33
|
|
|
30
34
|
private WindowManager mWindowManager;
|
|
31
35
|
private WindowManager.LayoutParams mLayoutParams;
|
|
32
36
|
private SurfaceView mSurfaceView;
|
|
33
37
|
private View mSmallWindowView;
|
|
34
38
|
private ActivityLaunchReceiver mActivityLaunchReceiver;
|
|
39
|
+
private FloatingOnTouchListener
|
|
40
|
+
mFloatingOnTouchListener; // Store the listener instance
|
|
35
41
|
|
|
36
42
|
public static final String ACTION_STOP_PIP_SERVICE =
|
|
37
43
|
"com.volcengine.velive.rn.pull.STOP_PIP_SERVICE";
|
|
@@ -97,37 +103,68 @@ public class FloatingWindowService extends Service {
|
|
|
97
103
|
int screenWidth = displayMetrics.widthPixels;
|
|
98
104
|
int screenHeight = displayMetrics.heightPixels;
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
int
|
|
106
|
+
int drawableWidth = screenWidth - MARGIN_LEFT - MARGIN_RIGHT;
|
|
107
|
+
int drawableHeight = screenHeight - MARGIN_TOP - MARGIN_BOTTOM;
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
int maxLen
|
|
109
|
+
boolean isPortrait = screenHeight > screenWidth;
|
|
110
|
+
|
|
111
|
+
int maxLen;
|
|
112
|
+
if (isPortrait) {
|
|
113
|
+
// 竖屏时,宽度不能超过 drawableWidth
|
|
114
|
+
maxLen = drawableWidth;
|
|
115
|
+
} else {
|
|
116
|
+
// 横屏时,高度不能超过 drawableHeight
|
|
117
|
+
maxLen = drawableHeight;
|
|
118
|
+
}
|
|
106
119
|
|
|
107
|
-
// Limit the floating window size to prevent it from being too large or too
|
|
108
|
-
// small, control the longer side to maxLen, scale the shorter
|
|
109
|
-
// side proportionally
|
|
110
120
|
int width, height;
|
|
111
|
-
if (aspectRatio >= 1) {
|
|
112
|
-
height = (int)(maxLen / aspectRatio);
|
|
121
|
+
if (aspectRatio >= 1) { // 宽大于等于高 (横向视频或方形)
|
|
113
122
|
width = maxLen;
|
|
114
|
-
|
|
115
|
-
width
|
|
123
|
+
height = (int)(width / aspectRatio);
|
|
124
|
+
if (isPortrait && width > drawableWidth) {
|
|
125
|
+
width = drawableWidth;
|
|
126
|
+
height = (int)(width / aspectRatio);
|
|
127
|
+
}
|
|
128
|
+
if (!isPortrait &&
|
|
129
|
+
height > drawableHeight) { // 横屏下,若计算出的高度超出drawableHeight
|
|
130
|
+
height = drawableHeight;
|
|
131
|
+
width = (int)(height * aspectRatio);
|
|
132
|
+
}
|
|
133
|
+
} else { // 高大于宽 (竖向视频)
|
|
116
134
|
height = maxLen;
|
|
135
|
+
width = (int)(height * aspectRatio);
|
|
136
|
+
if (!isPortrait && height > drawableHeight) {
|
|
137
|
+
height = drawableHeight;
|
|
138
|
+
width = (int)(height * aspectRatio);
|
|
139
|
+
}
|
|
140
|
+
if (isPortrait &&
|
|
141
|
+
width > drawableWidth) { // 竖屏下,若计算出的宽度超出drawableWidth
|
|
142
|
+
width = drawableWidth;
|
|
143
|
+
height = (int)(width / aspectRatio);
|
|
144
|
+
}
|
|
117
145
|
}
|
|
118
|
-
mLayoutParams.width = width;
|
|
119
|
-
mLayoutParams.height = height;
|
|
120
146
|
|
|
121
|
-
//
|
|
122
|
-
mLayoutParams.
|
|
123
|
-
mLayoutParams.
|
|
147
|
+
// Ensure dimensions are positive
|
|
148
|
+
mLayoutParams.width = Math.max(1, width);
|
|
149
|
+
mLayoutParams.height = Math.max(1, height);
|
|
150
|
+
|
|
151
|
+
// Initial position of the floating window, respecting margins
|
|
152
|
+
mLayoutParams.x =
|
|
153
|
+
Math.max(MARGIN_LEFT,
|
|
154
|
+
Math.min(x, screenWidth - mLayoutParams.width - MARGIN_RIGHT));
|
|
155
|
+
mLayoutParams.y =
|
|
156
|
+
Math.max(MARGIN_TOP, Math.min(y, screenHeight - mLayoutParams.height -
|
|
157
|
+
MARGIN_BOTTOM));
|
|
124
158
|
|
|
125
159
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
126
160
|
if (Settings.canDrawOverlays(this)) {
|
|
127
161
|
LayoutInflater layoutInflater = LayoutInflater.from(this);
|
|
128
162
|
mSmallWindowView =
|
|
129
163
|
layoutInflater.inflate(R.layout.floating_window_layout, null);
|
|
130
|
-
|
|
164
|
+
this.mFloatingOnTouchListener =
|
|
165
|
+
new FloatingOnTouchListener(); // Create and store the instance
|
|
166
|
+
mSmallWindowView.setOnTouchListener(
|
|
167
|
+
this.mFloatingOnTouchListener); // Set the stored instance
|
|
131
168
|
mWindowManager.addView(mSmallWindowView, mLayoutParams);
|
|
132
169
|
mSurfaceView = mSmallWindowView.findViewById(R.id.surface_view);
|
|
133
170
|
mSmallWindowView.findViewById(R.id.surface_close_btn)
|
|
@@ -204,6 +241,8 @@ public class FloatingWindowService extends Service {
|
|
|
204
241
|
}
|
|
205
242
|
|
|
206
243
|
private class FloatingOnTouchListener implements View.OnTouchListener {
|
|
244
|
+
private boolean isDoubleClickAnimating =
|
|
245
|
+
false; // Flag to indicate if double-click animation is running
|
|
207
246
|
private final int touchSlop =
|
|
208
247
|
ViewConfiguration
|
|
209
248
|
.get(FloatingWindowService.this.getApplicationContext())
|
|
@@ -212,36 +251,289 @@ public class FloatingWindowService extends Service {
|
|
|
212
251
|
private int x, y;
|
|
213
252
|
private boolean isDragging;
|
|
214
253
|
|
|
254
|
+
private static final int MODE_NONE = 0;
|
|
255
|
+
private static final int MODE_DRAG = 1;
|
|
256
|
+
private static final int MODE_ZOOM = 2;
|
|
257
|
+
private int mode = MODE_NONE;
|
|
258
|
+
|
|
259
|
+
private float oldDist = 1f;
|
|
260
|
+
private float initialAspectRatio;
|
|
261
|
+
private int initialWidth;
|
|
262
|
+
private int initialHeight;
|
|
263
|
+
|
|
264
|
+
// Screen metrics
|
|
265
|
+
private DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
266
|
+
private int screenWidth;
|
|
267
|
+
private int screenHeight;
|
|
268
|
+
private int drawableWidth;
|
|
269
|
+
private int drawableHeight;
|
|
270
|
+
private boolean isPortraitScreen;
|
|
271
|
+
|
|
272
|
+
private long lastClickTime = 0;
|
|
273
|
+
private static final long DOUBLE_CLICK_TIME_DELTA = 200; // milliseconds
|
|
274
|
+
|
|
275
|
+
private final float ZOOM_SCALE_FACTOR =
|
|
276
|
+
1.5f; // Factor to zoom in/out on double tap
|
|
277
|
+
|
|
278
|
+
private void updateScreenMetrics() {
|
|
279
|
+
mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);
|
|
280
|
+
screenWidth = displayMetrics.widthPixels;
|
|
281
|
+
screenHeight = displayMetrics.heightPixels;
|
|
282
|
+
drawableWidth = screenWidth - MARGIN_LEFT - MARGIN_RIGHT;
|
|
283
|
+
drawableHeight = screenHeight - MARGIN_TOP - MARGIN_BOTTOM;
|
|
284
|
+
isPortraitScreen = screenHeight > screenWidth;
|
|
285
|
+
}
|
|
286
|
+
|
|
215
287
|
@Override
|
|
216
288
|
public boolean onTouch(View view, MotionEvent event) {
|
|
217
|
-
|
|
289
|
+
// Update screen metrics at the beginning of a touch gesture,
|
|
290
|
+
// as orientation or screen size might have changed since last touch.
|
|
291
|
+
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
292
|
+
updateScreenMetrics();
|
|
293
|
+
}
|
|
294
|
+
// If currently animating from double click, consume touch events to
|
|
295
|
+
// prevent interference
|
|
296
|
+
if (isDoubleClickAnimating) {
|
|
297
|
+
// Allow ACTION_UP to potentially clear the flag if animation ends early
|
|
298
|
+
// or is cancelled but primarily, prevent new gestures from starting.
|
|
299
|
+
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
300
|
+
mode = MODE_NONE; // Prevent drag/zoom from starting
|
|
301
|
+
}
|
|
302
|
+
return true; // Consume the event
|
|
303
|
+
}
|
|
304
|
+
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
|
305
|
+
case MotionEvent.ACTION_DOWN:
|
|
306
|
+
downX = x = (int)event.getRawX();
|
|
307
|
+
downY = y = (int)event.getRawY();
|
|
308
|
+
mode = MODE_DRAG;
|
|
309
|
+
initialAspectRatio = (float)mLayoutParams.width / mLayoutParams.height;
|
|
310
|
+
initialWidth = mLayoutParams.width;
|
|
311
|
+
initialHeight = mLayoutParams.height;
|
|
312
|
+
|
|
313
|
+
// Double tap detection
|
|
314
|
+
long clickTime = System.currentTimeMillis();
|
|
315
|
+
if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA) {
|
|
316
|
+
handleDoubleClick(view);
|
|
317
|
+
}
|
|
318
|
+
lastClickTime = clickTime;
|
|
319
|
+
break;
|
|
320
|
+
case MotionEvent.ACTION_POINTER_DOWN:
|
|
321
|
+
oldDist = spacing(event);
|
|
322
|
+
if (oldDist > 10f) {
|
|
323
|
+
mode = MODE_ZOOM;
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
218
326
|
case MotionEvent.ACTION_UP:
|
|
327
|
+
case MotionEvent.ACTION_POINTER_UP:
|
|
328
|
+
boolean wasZooming = (mode == MODE_ZOOM);
|
|
329
|
+
// Capture params before mode is reset
|
|
330
|
+
int xBeforeSnap = mLayoutParams.x;
|
|
331
|
+
int widthBeforeSnap = mLayoutParams.width;
|
|
332
|
+
|
|
333
|
+
mode = MODE_NONE; // Reset mode
|
|
334
|
+
|
|
219
335
|
if (isDragging) {
|
|
220
336
|
isDragging = false;
|
|
337
|
+
// Perform snap animation if it was a drag or end of zoom
|
|
338
|
+
if (event.getPointerCount() == 1 || wasZooming) {
|
|
339
|
+
Log.i(TAG, "ACTION_UP/POINTER_UP: Triggering snap. xBeforeSnap=" +
|
|
340
|
+
xBeforeSnap + ", widthBeforeSnap=" +
|
|
341
|
+
widthBeforeSnap + ", wasZooming=" + wasZooming +
|
|
342
|
+
", pointerCount=" + event.getPointerCount());
|
|
343
|
+
animateSnapToEdge(view);
|
|
344
|
+
}
|
|
221
345
|
} else {
|
|
222
|
-
|
|
223
|
-
|
|
346
|
+
// Only trigger click if not zooming and it's a single pointer up
|
|
347
|
+
if (event.getPointerCount() == 1 &&
|
|
348
|
+
(event.getAction() & MotionEvent.ACTION_MASK) ==
|
|
349
|
+
MotionEvent.ACTION_UP) {
|
|
350
|
+
// Check if it was a drag or a tap
|
|
351
|
+
if (Math.abs(event.getRawX() - downX) < touchSlop &&
|
|
352
|
+
Math.abs(event.getRawY() - downY) < touchSlop) {
|
|
353
|
+
// This is a tap, not a double tap, so handle as single click if
|
|
354
|
+
// needed For now, double tap handles zoom, single tap is handled
|
|
355
|
+
// by new_window_btn
|
|
356
|
+
}
|
|
357
|
+
}
|
|
224
358
|
}
|
|
225
|
-
downX
|
|
226
|
-
break;
|
|
227
|
-
case MotionEvent.ACTION_DOWN:
|
|
228
|
-
downX = x = (int)event.getRawX();
|
|
229
|
-
downY = y = (int)event.getRawY();
|
|
359
|
+
// downX and downY are reset at ACTION_DOWN for the primary pointer
|
|
230
360
|
break;
|
|
361
|
+
|
|
231
362
|
case MotionEvent.ACTION_MOVE:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
363
|
+
if (mode == MODE_DRAG && event.getPointerCount() == 1) {
|
|
364
|
+
int nowX = (int)event.getRawX();
|
|
365
|
+
int nowY = (int)event.getRawY();
|
|
366
|
+
if (!isDragging && (Math.abs(nowX - downX) > touchSlop ||
|
|
367
|
+
Math.abs(nowY - downY) > touchSlop)) {
|
|
368
|
+
isDragging = true;
|
|
369
|
+
}
|
|
370
|
+
if (isDragging) {
|
|
371
|
+
int movedX = nowX - x;
|
|
372
|
+
int movedY = nowY - y;
|
|
373
|
+
mLayoutParams.x = mLayoutParams.x + movedX;
|
|
374
|
+
mLayoutParams.y = mLayoutParams.y + movedY;
|
|
375
|
+
|
|
376
|
+
// 使用预先获取的屏幕高度 screenHeight
|
|
377
|
+
// 限制y坐标在安全区域内
|
|
378
|
+
mLayoutParams.y = Math.max(
|
|
379
|
+
MARGIN_TOP, Math.min(mLayoutParams.y, this.screenHeight -
|
|
380
|
+
mLayoutParams.height -
|
|
381
|
+
MARGIN_BOTTOM));
|
|
382
|
+
|
|
383
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
384
|
+
}
|
|
385
|
+
x = nowX;
|
|
386
|
+
y = nowY;
|
|
387
|
+
} else if (mode == MODE_ZOOM && event.getPointerCount() >= 2) {
|
|
388
|
+
float newDist = spacing(event);
|
|
389
|
+
if (newDist > 10f) {
|
|
390
|
+
// Calculate midpoint of the two fingers
|
|
391
|
+
float midX = (event.getX(0) + event.getX(1)) / 2;
|
|
392
|
+
float midY = (event.getY(0) + event.getY(1)) / 2;
|
|
393
|
+
|
|
394
|
+
// Convert midpoint from view coordinates to screen coordinates
|
|
395
|
+
float screenMidX = mLayoutParams.x + midX;
|
|
396
|
+
float screenMidY = mLayoutParams.y + midY;
|
|
397
|
+
|
|
398
|
+
float scale = newDist / oldDist;
|
|
399
|
+
int oldWidth = mLayoutParams.width;
|
|
400
|
+
int oldHeight = mLayoutParams.height;
|
|
401
|
+
int newWidth = (int)(oldWidth * scale);
|
|
402
|
+
int newHeight = (int)(oldHeight * scale);
|
|
403
|
+
|
|
404
|
+
// Use pre-calculated screen metrics
|
|
405
|
+
// Screen metrics are now updated in onTouch ACTION_DOWN, so they
|
|
406
|
+
// should be current here.
|
|
407
|
+
|
|
408
|
+
// Min size constraints: 1/2 of initial size, but not smaller than a
|
|
409
|
+
// fraction of screen respecting margins
|
|
410
|
+
int minAllowedWidth;
|
|
411
|
+
int minAllowedHeight;
|
|
412
|
+
if (isPortraitScreen) {
|
|
413
|
+
minAllowedWidth = Math.max(initialWidth / 2, drawableWidth / 2);
|
|
414
|
+
minAllowedHeight = (int)(minAllowedWidth / initialAspectRatio);
|
|
415
|
+
} else {
|
|
416
|
+
minAllowedHeight =
|
|
417
|
+
Math.max(initialHeight / 2, drawableHeight / 2);
|
|
418
|
+
minAllowedWidth = (int)(minAllowedHeight * initialAspectRatio);
|
|
419
|
+
}
|
|
420
|
+
// Ensure min dimensions are at least 1
|
|
421
|
+
minAllowedWidth = Math.max(1, minAllowedWidth);
|
|
422
|
+
minAllowedHeight = Math.max(1, minAllowedHeight);
|
|
423
|
+
|
|
424
|
+
// Max size constraints based on orientation and margins
|
|
425
|
+
int maxAllowedWidth, maxAllowedHeight;
|
|
426
|
+
if (isPortraitScreen) {
|
|
427
|
+
maxAllowedWidth = drawableWidth;
|
|
428
|
+
maxAllowedHeight =
|
|
429
|
+
(int)(maxAllowedWidth /
|
|
430
|
+
initialAspectRatio); // Maintain aspect ratio
|
|
431
|
+
if (maxAllowedHeight >
|
|
432
|
+
drawableHeight) { // If calculated height is too much for
|
|
433
|
+
// portrait
|
|
434
|
+
maxAllowedHeight = drawableHeight;
|
|
435
|
+
maxAllowedWidth = (int)(maxAllowedHeight * initialAspectRatio);
|
|
436
|
+
}
|
|
437
|
+
} else { // Landscape screen
|
|
438
|
+
maxAllowedHeight = drawableHeight;
|
|
439
|
+
maxAllowedWidth =
|
|
440
|
+
(int)(maxAllowedHeight *
|
|
441
|
+
initialAspectRatio); // Maintain aspect ratio
|
|
442
|
+
if (maxAllowedWidth > drawableWidth) { // If calculated width is
|
|
443
|
+
// too much for landscape
|
|
444
|
+
maxAllowedWidth = drawableWidth;
|
|
445
|
+
maxAllowedHeight = (int)(maxAllowedWidth / initialAspectRatio);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// maxAllowedWidth = Math.min(maxAllowedWidth, LONGER_SIDE_MAX_LEN);
|
|
449
|
+
// // Global max len - Removed maxAllowedHeight =
|
|
450
|
+
// Math.min(maxAllowedHeight, LONGER_SIDE_MAX_LEN); // Global max
|
|
451
|
+
// len - Removed
|
|
452
|
+
if (initialAspectRatio >= 1)
|
|
453
|
+
maxAllowedHeight = (int)(maxAllowedWidth / initialAspectRatio);
|
|
454
|
+
else
|
|
455
|
+
maxAllowedWidth = (int)(maxAllowedHeight * initialAspectRatio);
|
|
456
|
+
|
|
457
|
+
// Apply scaling
|
|
458
|
+
newWidth = (int)(oldWidth * scale);
|
|
459
|
+
newHeight = (int)(oldHeight * scale);
|
|
460
|
+
|
|
461
|
+
// Clamp to min/max size while maintaining aspect ratio
|
|
462
|
+
if (scale < 1) { // Shrinking
|
|
463
|
+
newWidth = Math.max(minAllowedWidth, newWidth);
|
|
464
|
+
newHeight = (int)(newWidth / initialAspectRatio);
|
|
465
|
+
if (newHeight < minAllowedHeight) {
|
|
466
|
+
newHeight = minAllowedHeight;
|
|
467
|
+
newWidth = (int)(newHeight * initialAspectRatio);
|
|
468
|
+
}
|
|
469
|
+
} else { // Expanding
|
|
470
|
+
newWidth = Math.min(maxAllowedWidth, newWidth);
|
|
471
|
+
newHeight = (int)(newWidth / initialAspectRatio);
|
|
472
|
+
if (newHeight > maxAllowedHeight) {
|
|
473
|
+
newHeight = maxAllowedHeight;
|
|
474
|
+
newWidth = (int)(newHeight * initialAspectRatio);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Final check to ensure dimensions are within absolute min/max
|
|
479
|
+
// bounds
|
|
480
|
+
newWidth =
|
|
481
|
+
Math.max(minAllowedWidth, Math.min(newWidth, maxAllowedWidth));
|
|
482
|
+
newHeight =
|
|
483
|
+
(int)(newWidth / initialAspectRatio); // Maintain aspect ratio
|
|
484
|
+
// Ensure height is also clamped correctly after width adjustment
|
|
485
|
+
if (newHeight < minAllowedHeight) {
|
|
486
|
+
newHeight = minAllowedHeight;
|
|
487
|
+
newWidth = (int)(newHeight * initialAspectRatio);
|
|
488
|
+
}
|
|
489
|
+
if (newHeight > maxAllowedHeight) {
|
|
490
|
+
newHeight = maxAllowedHeight;
|
|
491
|
+
newWidth = (int)(newHeight * initialAspectRatio);
|
|
492
|
+
}
|
|
493
|
+
// Re-clamp width just in case height clamping affected it and
|
|
494
|
+
// pushed it out of width bounds
|
|
495
|
+
newWidth =
|
|
496
|
+
Math.max(minAllowedWidth, Math.min(newWidth, maxAllowedWidth));
|
|
497
|
+
|
|
498
|
+
// Adjust position to keep the zoom centered on the midpoint
|
|
499
|
+
mLayoutParams.x = (int)(screenMidX - (midX * newWidth / oldWidth));
|
|
500
|
+
mLayoutParams.y =
|
|
501
|
+
(int)(screenMidY - (midY * newHeight / oldHeight));
|
|
502
|
+
|
|
503
|
+
mLayoutParams.width = newWidth;
|
|
504
|
+
mLayoutParams.height = newHeight;
|
|
505
|
+
|
|
506
|
+
// Prevent window from going off-screen after repositioning
|
|
507
|
+
// For Y, keep margin constraints
|
|
508
|
+
mLayoutParams.y = Math.max(
|
|
509
|
+
MARGIN_TOP,
|
|
510
|
+
Math.min(mLayoutParams.y,
|
|
511
|
+
screenHeight - mLayoutParams.height - MARGIN_BOTTOM));
|
|
512
|
+
// For X, constrain to screen physical edges only during zoom,
|
|
513
|
+
// let animateSnapToEdge handle final margin snapping.
|
|
514
|
+
mLayoutParams.x = Math.max(
|
|
515
|
+
0, // Screen physical left edge
|
|
516
|
+
Math.min(
|
|
517
|
+
mLayoutParams.x,
|
|
518
|
+
screenWidth -
|
|
519
|
+
mLayoutParams.width)); // Screen physical right edge
|
|
520
|
+
|
|
521
|
+
// 更新SurfaceView的布局参数
|
|
522
|
+
if (mSurfaceView != null) {
|
|
523
|
+
android.view.ViewGroup.LayoutParams surfaceParams =
|
|
524
|
+
mSurfaceView.getLayoutParams();
|
|
525
|
+
if (surfaceParams != null) {
|
|
526
|
+
surfaceParams.width = mLayoutParams.width;
|
|
527
|
+
surfaceParams.height = mLayoutParams.height;
|
|
528
|
+
mSurfaceView.setLayoutParams(surfaceParams);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
532
|
+
oldDist = newDist;
|
|
533
|
+
}
|
|
534
|
+
isDragging =
|
|
535
|
+
true; // Zooming implies dragging state for click handling
|
|
536
|
+
}
|
|
245
537
|
break;
|
|
246
538
|
|
|
247
539
|
default:
|
|
@@ -249,5 +541,392 @@ public class FloatingWindowService extends Service {
|
|
|
249
541
|
}
|
|
250
542
|
return true;
|
|
251
543
|
}
|
|
544
|
+
|
|
545
|
+
private float spacing(MotionEvent event) {
|
|
546
|
+
if (event.getPointerCount() < 2)
|
|
547
|
+
return 0f;
|
|
548
|
+
float x = event.getX(0) - event.getX(1);
|
|
549
|
+
float y = event.getY(0) - event.getY(1);
|
|
550
|
+
return (float)Math.sqrt(x * x + y * y);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private void handleDoubleClick(View view) {
|
|
554
|
+
Log.d(TAG, "Double tap detected");
|
|
555
|
+
int targetWidth, targetHeight;
|
|
556
|
+
|
|
557
|
+
// Use pre-calculated screen metrics
|
|
558
|
+
// updateScreenMetrics(); // Ensure metrics are fresh if not updated in
|
|
559
|
+
// onTouch ACTION_DOWN Screen metrics are now updated in onTouch
|
|
560
|
+
// ACTION_DOWN, so they should be current here.
|
|
561
|
+
|
|
562
|
+
// Minimum size: 1/2 of initial size, but not smaller than a fraction of
|
|
563
|
+
// screen respecting margins
|
|
564
|
+
int minAllowedWidth = initialWidth / 2;
|
|
565
|
+
int minAllowedHeight = initialHeight / 2;
|
|
566
|
+
if (isPortraitScreen) {
|
|
567
|
+
minAllowedWidth = Math.max(minAllowedWidth, drawableWidth / 2);
|
|
568
|
+
minAllowedHeight = (int)(minAllowedWidth / initialAspectRatio);
|
|
569
|
+
} else {
|
|
570
|
+
minAllowedHeight = Math.max(minAllowedHeight, drawableHeight / 2);
|
|
571
|
+
minAllowedWidth = (int)(minAllowedHeight * initialAspectRatio);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Maximum size based on orientation and margins
|
|
575
|
+
int maxAllowedTargetWidth, maxAllowedTargetHeight;
|
|
576
|
+
if (isPortraitScreen) {
|
|
577
|
+
maxAllowedTargetWidth = drawableWidth;
|
|
578
|
+
maxAllowedTargetHeight =
|
|
579
|
+
(int)(maxAllowedTargetWidth / initialAspectRatio);
|
|
580
|
+
if (maxAllowedTargetHeight > drawableHeight) {
|
|
581
|
+
maxAllowedTargetHeight = drawableHeight;
|
|
582
|
+
maxAllowedTargetWidth =
|
|
583
|
+
(int)(maxAllowedTargetHeight * initialAspectRatio);
|
|
584
|
+
}
|
|
585
|
+
} else { // Landscape screen
|
|
586
|
+
maxAllowedTargetHeight = drawableHeight;
|
|
587
|
+
maxAllowedTargetWidth =
|
|
588
|
+
(int)(maxAllowedTargetHeight * initialAspectRatio);
|
|
589
|
+
if (maxAllowedTargetWidth > drawableWidth) {
|
|
590
|
+
maxAllowedTargetWidth = drawableWidth;
|
|
591
|
+
maxAllowedTargetHeight =
|
|
592
|
+
(int)(maxAllowedTargetWidth / initialAspectRatio);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// Apply global LONGER_SIDE_MAX_LEN constraint - Removed
|
|
596
|
+
// if (initialAspectRatio >= 1) { // Landscape or square video
|
|
597
|
+
// maxAllowedTargetWidth =
|
|
598
|
+
// Math.min(maxAllowedTargetWidth, LONGER_SIDE_MAX_LEN);
|
|
599
|
+
// maxAllowedTargetHeight =
|
|
600
|
+
// (int)(maxAllowedTargetWidth / initialAspectRatio);
|
|
601
|
+
// } else { // Portrait video
|
|
602
|
+
// maxAllowedTargetHeight =
|
|
603
|
+
// Math.min(maxAllowedTargetHeight, LONGER_SIDE_MAX_LEN);
|
|
604
|
+
// maxAllowedTargetWidth =
|
|
605
|
+
// (int)(maxAllowedTargetHeight * initialAspectRatio);
|
|
606
|
+
// }
|
|
607
|
+
|
|
608
|
+
int currentX = mLayoutParams.x;
|
|
609
|
+
int currentY = mLayoutParams.y;
|
|
610
|
+
int currentWidth = mLayoutParams.width;
|
|
611
|
+
int currentHeight = mLayoutParams.height;
|
|
612
|
+
|
|
613
|
+
int finalTargetX = currentX;
|
|
614
|
+
int finalTargetY = currentY;
|
|
615
|
+
|
|
616
|
+
// 计算当前宽度与中间值的比较,决定缩放方向
|
|
617
|
+
int midWidth = (minAllowedWidth + maxAllowedTargetWidth) / 2;
|
|
618
|
+
if (currentWidth < midWidth) {
|
|
619
|
+
// 当前更接近最小值,放大到最大尺寸
|
|
620
|
+
targetWidth = maxAllowedTargetWidth;
|
|
621
|
+
targetHeight = maxAllowedTargetHeight;
|
|
622
|
+
Log.d(TAG, "Zooming in to max: " + targetWidth + "x" + targetHeight);
|
|
623
|
+
} else {
|
|
624
|
+
// 当前更接近最大值,缩小到最小尺寸
|
|
625
|
+
targetWidth = minAllowedWidth;
|
|
626
|
+
targetHeight = minAllowedHeight;
|
|
627
|
+
Log.d(TAG, "Zooming out to min: " + targetWidth + "x" + targetHeight);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Ensure target dimensions respect calculated min/max and screen
|
|
631
|
+
// boundaries with margins
|
|
632
|
+
targetWidth = Math.max(minAllowedWidth,
|
|
633
|
+
Math.min(targetWidth, maxAllowedTargetWidth));
|
|
634
|
+
targetHeight = (int)(targetWidth / initialAspectRatio);
|
|
635
|
+
if (targetHeight < minAllowedHeight) {
|
|
636
|
+
targetHeight = minAllowedHeight;
|
|
637
|
+
targetWidth = (int)(targetHeight * initialAspectRatio);
|
|
638
|
+
}
|
|
639
|
+
if (targetHeight > maxAllowedTargetHeight) {
|
|
640
|
+
targetHeight = maxAllowedTargetHeight;
|
|
641
|
+
targetWidth = (int)(targetHeight * initialAspectRatio);
|
|
642
|
+
}
|
|
643
|
+
// Final check for width after height adjustment
|
|
644
|
+
targetWidth = Math.max(minAllowedWidth,
|
|
645
|
+
Math.min(targetWidth, maxAllowedTargetWidth));
|
|
646
|
+
|
|
647
|
+
// Instantly apply new size and update layout BEFORE starting
|
|
648
|
+
// position/size animations This helps in reducing black bars by setting
|
|
649
|
+
// the target container size first.
|
|
650
|
+
mLayoutParams.width = targetWidth;
|
|
651
|
+
mLayoutParams.height = targetHeight;
|
|
652
|
+
if (mSmallWindowView != null &&
|
|
653
|
+
mSmallWindowView.getWindowToken() != null) {
|
|
654
|
+
mWindowManager.updateViewLayout(mSmallWindowView, mLayoutParams);
|
|
655
|
+
// Also update SurfaceView's layout params to match the new window size
|
|
656
|
+
// immediately
|
|
657
|
+
if (mSurfaceView != null) {
|
|
658
|
+
android.view.ViewGroup.LayoutParams surfaceParams =
|
|
659
|
+
mSurfaceView.getLayoutParams();
|
|
660
|
+
if (surfaceParams != null) {
|
|
661
|
+
surfaceParams.width = targetWidth;
|
|
662
|
+
surfaceParams.height = targetHeight;
|
|
663
|
+
mSurfaceView.setLayoutParams(surfaceParams);
|
|
664
|
+
mSurfaceView
|
|
665
|
+
.requestLayout(); // Crucial for SurfaceView to redraw correctly
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Determine window corner for zoom anchor, considering margins
|
|
671
|
+
boolean isTop =
|
|
672
|
+
currentY < (screenHeight - MARGIN_TOP - MARGIN_BOTTOM) / 2 -
|
|
673
|
+
currentHeight / 2 + MARGIN_TOP;
|
|
674
|
+
boolean isLeft =
|
|
675
|
+
currentX < (screenWidth - MARGIN_LEFT - MARGIN_RIGHT) / 2 -
|
|
676
|
+
currentWidth / 2 + MARGIN_LEFT;
|
|
677
|
+
|
|
678
|
+
if (isLeft && isTop) { // Top-left
|
|
679
|
+
finalTargetX = MARGIN_LEFT;
|
|
680
|
+
finalTargetY = MARGIN_TOP;
|
|
681
|
+
} else if (!isLeft && isTop) { // Top-right
|
|
682
|
+
finalTargetX = screenWidth - targetWidth - MARGIN_RIGHT;
|
|
683
|
+
finalTargetY = MARGIN_TOP;
|
|
684
|
+
} else if (isLeft && !isTop) { // Bottom-left
|
|
685
|
+
finalTargetX = MARGIN_LEFT;
|
|
686
|
+
finalTargetY = screenHeight - targetHeight - MARGIN_BOTTOM;
|
|
687
|
+
} else { // Bottom-right
|
|
688
|
+
finalTargetX = screenWidth - targetWidth - MARGIN_RIGHT;
|
|
689
|
+
finalTargetY = screenHeight - targetHeight - MARGIN_BOTTOM;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Ensure target position is within drawable area
|
|
693
|
+
finalTargetX = Math.max(
|
|
694
|
+
MARGIN_LEFT,
|
|
695
|
+
Math.min(finalTargetX, screenWidth - targetWidth - MARGIN_RIGHT));
|
|
696
|
+
finalTargetY = Math.max(
|
|
697
|
+
MARGIN_TOP,
|
|
698
|
+
Math.min(finalTargetY, screenHeight - targetHeight - MARGIN_BOTTOM));
|
|
699
|
+
|
|
700
|
+
final int finalTargetWidth = targetWidth;
|
|
701
|
+
final int finalTargetHeight = targetHeight;
|
|
702
|
+
|
|
703
|
+
// Make copies for use in animator listeners
|
|
704
|
+
final int animFinalTargetX = finalTargetX;
|
|
705
|
+
final int animFinalTargetY = finalTargetY;
|
|
706
|
+
|
|
707
|
+
ValueAnimator xAnimator = ValueAnimator.ofInt(currentX, finalTargetX);
|
|
708
|
+
xAnimator.setDuration(200);
|
|
709
|
+
xAnimator.addUpdateListener(animation -> {
|
|
710
|
+
mLayoutParams.x = (Integer)animation.getAnimatedValue();
|
|
711
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
ValueAnimator yAnimator = ValueAnimator.ofInt(currentY, finalTargetY);
|
|
715
|
+
yAnimator.setDuration(200);
|
|
716
|
+
yAnimator.addUpdateListener(animation -> {
|
|
717
|
+
mLayoutParams.y = (Integer)animation.getAnimatedValue();
|
|
718
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
ValueAnimator widthAnimator =
|
|
722
|
+
ValueAnimator.ofInt(currentWidth, finalTargetWidth);
|
|
723
|
+
widthAnimator.setDuration(200);
|
|
724
|
+
widthAnimator.addUpdateListener(animation -> {
|
|
725
|
+
mLayoutParams.width = (Integer)animation.getAnimatedValue();
|
|
726
|
+
if (mSurfaceView != null) { // Ensure mSurfaceView is not null
|
|
727
|
+
android.view.ViewGroup.LayoutParams surfaceParams =
|
|
728
|
+
mSurfaceView.getLayoutParams();
|
|
729
|
+
if (surfaceParams != null) { // Ensure surfaceParams is not null
|
|
730
|
+
surfaceParams.width = mLayoutParams.width;
|
|
731
|
+
mSurfaceView.setLayoutParams(surfaceParams);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
ValueAnimator heightAnimator =
|
|
738
|
+
ValueAnimator.ofInt(currentHeight, finalTargetHeight);
|
|
739
|
+
heightAnimator.setDuration(200);
|
|
740
|
+
heightAnimator.addUpdateListener(animation -> {
|
|
741
|
+
mLayoutParams.height = (Integer)animation.getAnimatedValue();
|
|
742
|
+
if (mSurfaceView != null) { // Ensure mSurfaceView is not null
|
|
743
|
+
android.view.ViewGroup.LayoutParams surfaceParams =
|
|
744
|
+
mSurfaceView.getLayoutParams();
|
|
745
|
+
if (surfaceParams != null) { // Ensure surfaceParams is not null
|
|
746
|
+
surfaceParams.height = mLayoutParams.height;
|
|
747
|
+
mSurfaceView.setLayoutParams(surfaceParams);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
android.animation.AnimatorSet animatorSet =
|
|
754
|
+
new android.animation.AnimatorSet();
|
|
755
|
+
animatorSet.playTogether(xAnimator, yAnimator, widthAnimator,
|
|
756
|
+
heightAnimator);
|
|
757
|
+
// The isDoubleClickAnimating flag is now managed by
|
|
758
|
+
// FloatingOnTouchListener but we still need to set it within
|
|
759
|
+
// handleDoubleClick's AnimatorSet listeners to ensure it's active for the
|
|
760
|
+
// duration of this specific animation.
|
|
761
|
+
animatorSet.addListener(new android.animation.AnimatorListenerAdapter() {
|
|
762
|
+
@Override
|
|
763
|
+
public void onAnimationStart(android.animation.Animator animation) {
|
|
764
|
+
if (FloatingWindowService.this.mFloatingOnTouchListener != null) {
|
|
765
|
+
FloatingWindowService.this.mFloatingOnTouchListener
|
|
766
|
+
.isDoubleClickAnimating = true;
|
|
767
|
+
}
|
|
768
|
+
Log.d(TAG, "Double tap AnimatorSet started.");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
@Override
|
|
772
|
+
public void onAnimationEnd(android.animation.Animator animation) {
|
|
773
|
+
if (FloatingWindowService.this.mFloatingOnTouchListener != null) {
|
|
774
|
+
FloatingWindowService.this.mFloatingOnTouchListener
|
|
775
|
+
.isDoubleClickAnimating = false;
|
|
776
|
+
}
|
|
777
|
+
// ValueAnimators update mLayoutParams continuously via their
|
|
778
|
+
// AnimatorUpdateListener. For safety, explicitly set final position
|
|
779
|
+
// values as the last update might not be exact.
|
|
780
|
+
mLayoutParams.x = animFinalTargetX;
|
|
781
|
+
mLayoutParams.y = animFinalTargetY;
|
|
782
|
+
// mLayoutParams.width and mLayoutParams.height should be at
|
|
783
|
+
// finalTargetWidth/Height due to widthAnimator and heightAnimator's
|
|
784
|
+
// updates.
|
|
785
|
+
|
|
786
|
+
// Perform a final update of the window layout with all parameters.
|
|
787
|
+
if (mSmallWindowView != null &&
|
|
788
|
+
mSmallWindowView.getWindowToken() != null) {
|
|
789
|
+
mWindowManager.updateViewLayout(mSmallWindowView, mLayoutParams);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Resize SurfaceView (logic moved from postDelayed and refined)
|
|
793
|
+
if (mSurfaceView != null && mLayoutParams != null) {
|
|
794
|
+
android.view.ViewGroup.LayoutParams surfaceParams =
|
|
795
|
+
mSurfaceView.getLayoutParams();
|
|
796
|
+
if (surfaceParams != null) {
|
|
797
|
+
boolean changed = false;
|
|
798
|
+
if (surfaceParams.width != mLayoutParams.width) {
|
|
799
|
+
surfaceParams.width =
|
|
800
|
+
mLayoutParams.width; // mLayoutParams.width is at its final
|
|
801
|
+
// animated value
|
|
802
|
+
changed = true;
|
|
803
|
+
}
|
|
804
|
+
if (surfaceParams.height != mLayoutParams.height) {
|
|
805
|
+
surfaceParams.height =
|
|
806
|
+
mLayoutParams.height; // mLayoutParams.height is at its
|
|
807
|
+
// final animated value
|
|
808
|
+
changed = true;
|
|
809
|
+
}
|
|
810
|
+
if (changed) {
|
|
811
|
+
mSurfaceView.setLayoutParams(surfaceParams);
|
|
812
|
+
}
|
|
813
|
+
mSurfaceView
|
|
814
|
+
.requestLayout(); // Ensure the SurfaceView redraws correctly
|
|
815
|
+
Log.d(TAG,
|
|
816
|
+
"SurfaceView dimensions finalized in onAnimationEnd to: " +
|
|
817
|
+
mLayoutParams.width + "x" + mLayoutParams.height);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
Log.d(TAG, "Double tap AnimatorSet ended. Final state: x=" +
|
|
821
|
+
mLayoutParams.x + ", y=" + mLayoutParams.y + ", w=" +
|
|
822
|
+
mLayoutParams.width + ", h=" + mLayoutParams.height);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
@Override
|
|
826
|
+
public void onAnimationCancel(android.animation.Animator animation) {
|
|
827
|
+
if (FloatingWindowService.this.mFloatingOnTouchListener != null) {
|
|
828
|
+
FloatingWindowService.this.mFloatingOnTouchListener
|
|
829
|
+
.isDoubleClickAnimating = false;
|
|
830
|
+
}
|
|
831
|
+
// Animation was cancelled, so explicitly set all mLayoutParams to
|
|
832
|
+
// their final target values.
|
|
833
|
+
mLayoutParams.x = animFinalTargetX;
|
|
834
|
+
mLayoutParams.y = animFinalTargetY;
|
|
835
|
+
mLayoutParams.width = finalTargetWidth;
|
|
836
|
+
mLayoutParams.height = finalTargetHeight;
|
|
837
|
+
|
|
838
|
+
if (mSmallWindowView != null &&
|
|
839
|
+
mSmallWindowView.getWindowToken() != null) {
|
|
840
|
+
mWindowManager.updateViewLayout(mSmallWindowView, mLayoutParams);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Resize SurfaceView to final target dimensions
|
|
844
|
+
if (mSurfaceView != null && mLayoutParams != null) {
|
|
845
|
+
android.view.ViewGroup.LayoutParams surfaceParams =
|
|
846
|
+
mSurfaceView.getLayoutParams();
|
|
847
|
+
if (surfaceParams != null) {
|
|
848
|
+
if (surfaceParams.width != mLayoutParams.width ||
|
|
849
|
+
surfaceParams.height != mLayoutParams.height) {
|
|
850
|
+
surfaceParams.width =
|
|
851
|
+
mLayoutParams.width; // Now finalTargetWidth
|
|
852
|
+
surfaceParams.height =
|
|
853
|
+
mLayoutParams.height; // Now finalTargetHeight
|
|
854
|
+
mSurfaceView.setLayoutParams(surfaceParams);
|
|
855
|
+
}
|
|
856
|
+
mSurfaceView
|
|
857
|
+
.requestLayout(); // Ensure the SurfaceView redraws correctly
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
Log.d(TAG,
|
|
861
|
+
"Double tap AnimatorSet cancelled. State set to targets: x=" +
|
|
862
|
+
mLayoutParams.x + ", y=" + mLayoutParams.y + ", w=" +
|
|
863
|
+
mLayoutParams.width + ", h=" + mLayoutParams.height);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
animatorSet.start();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
private void animateSnapToEdge(View view) {
|
|
870
|
+
// Use pre-calculated screen metrics
|
|
871
|
+
// updateScreenMetrics(); // Ensure metrics are fresh if not updated in
|
|
872
|
+
// onTouch ACTION_DOWN Screen metrics are now updated in onTouch
|
|
873
|
+
// ACTION_DOWN, so they should be current here.
|
|
874
|
+
final int screenWidth = this.screenWidth; // Make final for use in lambda
|
|
875
|
+
final int windowWidthAtAnimationStart =
|
|
876
|
+
mLayoutParams.width; // Capture width at start
|
|
877
|
+
|
|
878
|
+
int currentX =
|
|
879
|
+
mLayoutParams
|
|
880
|
+
.x; // This is the x at the moment animateSnapToEdge is called
|
|
881
|
+
int targetX;
|
|
882
|
+
|
|
883
|
+
// Determine targetX based on current position and captured width
|
|
884
|
+
if (currentX + windowWidthAtAnimationStart / 2 < screenWidth / 2) {
|
|
885
|
+
targetX = MARGIN_LEFT;
|
|
886
|
+
} else {
|
|
887
|
+
targetX = screenWidth - windowWidthAtAnimationStart - MARGIN_RIGHT;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
Log.i(TAG, "animateSnapToEdge: currentX=" + currentX +
|
|
891
|
+
", windowWidthAtAnimStart=" + windowWidthAtAnimationStart +
|
|
892
|
+
", screenWidth=" + screenWidth + ", calculatedTargetX=" +
|
|
893
|
+
targetX + ", currentY=" + mLayoutParams.y +
|
|
894
|
+
", currentHeight=" + mLayoutParams.height);
|
|
895
|
+
|
|
896
|
+
if (currentX == targetX) {
|
|
897
|
+
Log.i(TAG, "animateSnapToEdge: Already at targetX (" + targetX +
|
|
898
|
+
"). No animation needed.");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
ValueAnimator snapAnimator = ValueAnimator.ofInt(currentX, targetX);
|
|
903
|
+
snapAnimator.setDuration(200);
|
|
904
|
+
snapAnimator.addUpdateListener(animation -> {
|
|
905
|
+
mLayoutParams.x = (Integer)animation.getAnimatedValue();
|
|
906
|
+
// Boundary checks using width captured at animation start
|
|
907
|
+
if (mLayoutParams.x < 0) { // Physical screen boundary
|
|
908
|
+
mLayoutParams.x = 0;
|
|
909
|
+
}
|
|
910
|
+
// Ensure the right edge does not go beyond screen width
|
|
911
|
+
if (mLayoutParams.x + windowWidthAtAnimationStart >
|
|
912
|
+
screenWidth) { // Physical screen boundary
|
|
913
|
+
mLayoutParams.x = screenWidth - windowWidthAtAnimationStart;
|
|
914
|
+
}
|
|
915
|
+
try {
|
|
916
|
+
if (mSmallWindowView != null &&
|
|
917
|
+
mSmallWindowView.getWindowToken() !=
|
|
918
|
+
null) { // Check if view is still attached
|
|
919
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
920
|
+
}
|
|
921
|
+
} catch (IllegalArgumentException e) {
|
|
922
|
+
Log.w(TAG, "animateSnapToEdge: View not attached? " + e.getMessage());
|
|
923
|
+
// Cancel animator if view is gone to prevent further errors
|
|
924
|
+
if (animation.isRunning()) {
|
|
925
|
+
animation.cancel();
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
snapAnimator.start();
|
|
930
|
+
}
|
|
252
931
|
}
|
|
253
932
|
}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -6729,38 +6729,6 @@ var VeLivePlayerStatistics = function () {
|
|
|
6729
6729
|
enumerable: false,
|
|
6730
6730
|
configurable: true
|
|
6731
6731
|
});
|
|
6732
|
-
Object.defineProperty(VeLivePlayerStatistics_1.prototype, "format", {
|
|
6733
|
-
/** {zh}
|
|
6734
|
-
* @brief 当前播放的视频格式。
|
|
6735
|
-
*
|
|
6736
|
-
*/
|
|
6737
|
-
/** {en}
|
|
6738
|
-
* @brief The video format.
|
|
6739
|
-
*
|
|
6740
|
-
*/
|
|
6741
|
-
get: function () {
|
|
6742
|
-
// TODO: implement
|
|
6743
|
-
throw new Error("not implement 'format'");
|
|
6744
|
-
},
|
|
6745
|
-
enumerable: false,
|
|
6746
|
-
configurable: true
|
|
6747
|
-
});
|
|
6748
|
-
Object.defineProperty(VeLivePlayerStatistics_1.prototype, "protocol", {
|
|
6749
|
-
/** {zh}
|
|
6750
|
-
* @brief 当前播放的传输协议。
|
|
6751
|
-
*
|
|
6752
|
-
*/
|
|
6753
|
-
/** {en}
|
|
6754
|
-
* @brief The transmission protocol of the live stream.
|
|
6755
|
-
*
|
|
6756
|
-
*/
|
|
6757
|
-
get: function () {
|
|
6758
|
-
// TODO: implement
|
|
6759
|
-
throw new Error("not implement 'protocol'");
|
|
6760
|
-
},
|
|
6761
|
-
enumerable: false,
|
|
6762
|
-
configurable: true
|
|
6763
|
-
});
|
|
6764
6732
|
Object.defineProperty(VeLivePlayerStatistics_1.prototype, "url", {
|
|
6765
6733
|
/** {zh}
|
|
6766
6734
|
* @brief 当前直播的播放地址。
|
package/lib/module/index.js
CHANGED
|
@@ -6727,38 +6727,6 @@ var VeLivePlayerStatistics = function () {
|
|
|
6727
6727
|
enumerable: false,
|
|
6728
6728
|
configurable: true
|
|
6729
6729
|
});
|
|
6730
|
-
Object.defineProperty(VeLivePlayerStatistics_1.prototype, "format", {
|
|
6731
|
-
/** {zh}
|
|
6732
|
-
* @brief 当前播放的视频格式。
|
|
6733
|
-
*
|
|
6734
|
-
*/
|
|
6735
|
-
/** {en}
|
|
6736
|
-
* @brief The video format.
|
|
6737
|
-
*
|
|
6738
|
-
*/
|
|
6739
|
-
get: function () {
|
|
6740
|
-
// TODO: implement
|
|
6741
|
-
throw new Error("not implement 'format'");
|
|
6742
|
-
},
|
|
6743
|
-
enumerable: false,
|
|
6744
|
-
configurable: true
|
|
6745
|
-
});
|
|
6746
|
-
Object.defineProperty(VeLivePlayerStatistics_1.prototype, "protocol", {
|
|
6747
|
-
/** {zh}
|
|
6748
|
-
* @brief 当前播放的传输协议。
|
|
6749
|
-
*
|
|
6750
|
-
*/
|
|
6751
|
-
/** {en}
|
|
6752
|
-
* @brief The transmission protocol of the live stream.
|
|
6753
|
-
*
|
|
6754
|
-
*/
|
|
6755
|
-
get: function () {
|
|
6756
|
-
// TODO: implement
|
|
6757
|
-
throw new Error("not implement 'protocol'");
|
|
6758
|
-
},
|
|
6759
|
-
enumerable: false,
|
|
6760
|
-
configurable: true
|
|
6761
|
-
});
|
|
6762
6730
|
Object.defineProperty(VeLivePlayerStatistics_1.prototype, "url", {
|
|
6763
6731
|
/** {zh}
|
|
6764
6732
|
* @brief 当前直播的播放地址。
|
|
@@ -111,18 +111,6 @@ export declare class VeLivePlayerStatistics {
|
|
|
111
111
|
*/
|
|
112
112
|
get isHardwareDecode(): boolean;
|
|
113
113
|
|
|
114
|
-
/** {en}
|
|
115
|
-
* @brief The video format.
|
|
116
|
-
*
|
|
117
|
-
*/
|
|
118
|
-
get format(): void;
|
|
119
|
-
|
|
120
|
-
/** {en}
|
|
121
|
-
* @brief The transmission protocol of the live stream.
|
|
122
|
-
*
|
|
123
|
-
*/
|
|
124
|
-
get protocol(): void;
|
|
125
|
-
|
|
126
114
|
/** {en}
|
|
127
115
|
* @brief The current pull stream address.
|
|
128
116
|
*
|
|
@@ -1 +1,17 @@
|
|
|
1
|
+
declare module '../codegen/pack/keytype' {
|
|
2
|
+
interface VeLivePlayerStatistics {
|
|
3
|
+
|
|
4
|
+
/** {en}
|
|
5
|
+
* @brief The video format.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
format: VeLivePlayerFormat;
|
|
9
|
+
|
|
10
|
+
/** {en}
|
|
11
|
+
* @brief The transmission protocol of the live stream.
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
protocol: VeLivePlayerProtocol;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
1
17
|
export { VeLivePlayerAudioBufferType, VeLivePlayerAudioFrame, VeLivePlayerConfiguration, VeLivePlayerFillMode, VeLivePlayerFormat, VeLivePlayerLogLevel, VeLivePlayerMirror, VeLivePlayerPixelFormat, VeLivePlayerProtocol, VeLivePlayerResolution, VeLivePlayerResolutionSwitchReason, VeLivePlayerRotation, VeLivePlayerStatistics, VeLivePlayerStatus, VeLivePlayerStream, VeLivePlayerStreamData, VeLivePlayerStreamType, VeLivePlayerVideoBufferType, VeLivePlayerVideoFrame, } from '../codegen/pack/keytype';
|