tuikit-atomicx-vue3 3.3.2-beta.1 → 3.3.2

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.
@@ -135,8 +135,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
135
135
  default: withCtx(() => [
136
136
  createTextVNode(toDisplayString(unref(t)("Accept")), 1)
137
137
  ]),
138
- _: 2
139
- }, 1032, ["onClick"]),
138
+ _: 1
139
+ }, 8, ["onClick"]),
140
140
  createVNode(unref(TUIButton), {
141
141
  color: "red",
142
142
  onClick: ($event) => handleRejectCoGuestRequest(user.userId)
@@ -144,8 +144,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
144
144
  default: withCtx(() => [
145
145
  createTextVNode(toDisplayString(unref(t)("Reject")), 1)
146
146
  ]),
147
- _: 2
148
- }, 1032, ["onClick"])
147
+ _: 1
148
+ }, 8, ["onClick"])
149
149
  ])
150
150
  ])
151
151
  ]);
@@ -187,8 +187,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
187
187
  default: withCtx(() => [
188
188
  createTextVNode(toDisplayString(unref(t)("Disconnect")), 1)
189
189
  ]),
190
- _: 2
191
- }, 1032, ["onClick"])
190
+ _: 1
191
+ }, 8, ["onClick"])
192
192
  ])) : createCommentVNode("", true)
193
193
  ])
194
194
  ]);
@@ -1,4 +1,4 @@
1
- import { defineComponent, ref, computed, onUnmounted, createElementBlock, openBlock, normalizeStyle, createElementVNode, withDirectives, unref, createBlock, normalizeClass, toDisplayString, vShow } from "vue";
1
+ import { defineComponent, ref, computed, onUnmounted, createElementBlock, openBlock, normalizeStyle, createElementVNode, withDirectives, unref, createBlock, normalizeClass, toDisplayString, vShow, toRaw } from "vue";
2
2
  import { useUIKit, IconSpeakerOff, IconSpeakerOn } from "@tencentcloud/uikit-base-component-vue3";
3
3
  import { isMobile } from "../../../utils/env.js";
4
4
  import { _ as _export_sfc } from "../../../_plugin-vue_export-helper-1tPrXgE0.js";
@@ -44,8 +44,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
44
44
  height: `${props.iconSize || 20}px`
45
45
  }));
46
46
  const updateVolume = (newVolume) => {
47
+ volumeState.value.previous = toRaw(volumeState.value.current);
47
48
  volumeState.value.current = newVolume;
48
- volumeState.value.previous = newVolume;
49
49
  isMuted.value = newVolume === 0;
50
50
  emit("volume-change", newVolume);
51
51
  };
@@ -54,13 +54,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
54
54
  isMuted.value = false;
55
55
  volumeState.value.current = volumeState.value.previous || 0.2;
56
56
  emit("muted-change", false);
57
+ emit("volume-change", volumeState.value.current);
57
58
  } else {
58
59
  isMuted.value = true;
59
60
  volumeState.value.previous = volumeState.value.current;
60
61
  volumeState.value.current = 0;
61
62
  emit("muted-change", true);
63
+ emit("volume-change", volumeState.value.current);
62
64
  }
63
- updateVolume(volumeState.value.current);
64
65
  };
65
66
  const handleVolumeIconClick = () => {
66
67
  if (props.enableVolumeControl === false) {
@@ -245,7 +246,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
245
246
  };
246
247
  }
247
248
  });
248
- const AudioControl = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-81e72abd"]]);
249
+ const AudioControl = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-22bb0b88"]]);
249
250
  export {
250
251
  AudioControl as default
251
252
  };
@@ -14,6 +14,9 @@ const _hoisted_6 = ["title"];
14
14
  const AUTO_HIDE_DELAY = 3e3;
15
15
  const _sfc_main = /* @__PURE__ */ defineComponent({
16
16
  __name: "PlayerControl",
17
+ props: {
18
+ isLandscapeStyleMode: { type: Boolean }
19
+ },
17
20
  setup(__props) {
18
21
  const {
19
22
  isPlaying,
@@ -28,6 +31,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
28
31
  setVolume,
29
32
  cleanup
30
33
  } = usePlayerControlState();
34
+ const props = __props;
31
35
  const { t } = useUIKit();
32
36
  const currentVolume = ref(1);
33
37
  const isMuted = ref(false);
@@ -213,7 +217,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
213
217
  withDirectives(createElementVNode("div", {
214
218
  ref_key: "playerControlRef",
215
219
  ref: playerControlRef,
216
- class: normalizeClass(["playback-controls", unref(isMobile) ? "mobile-mode" : "pc-mode"])
220
+ class: normalizeClass([
221
+ "playback-controls",
222
+ unref(isMobile) ? "mobile-mode" : "pc-mode",
223
+ { "mobile-landscape-mode": props.isLandscapeStyleMode }
224
+ ])
217
225
  }, [
218
226
  createElementVNode("div", _hoisted_1, [
219
227
  createElementVNode("span", {
@@ -265,7 +273,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
265
273
  };
266
274
  }
267
275
  });
268
- const PlayerControl = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-cb28a3fe"]]);
276
+ const PlayerControl = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-28ed6eb9"]]);
269
277
  export {
270
278
  PlayerControl as default
271
279
  };
@@ -1,2 +1,15 @@
1
- declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
1
+ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
2
+ isLandscapeStyleMode?: boolean;
3
+ }>>, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
4
+ isLandscapeStyleMode?: boolean;
5
+ }>>> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
2
6
  export default _default;
7
+ type __VLS_NonUndefinedable<T> = T extends undefined ? never : T;
8
+ type __VLS_TypePropsToRuntimeProps<T> = {
9
+ [K in keyof T]-?: {} extends Pick<T, K> ? {
10
+ type: import('vue').PropType<__VLS_NonUndefinedable<T[K]>>;
11
+ } : {
12
+ type: import('vue').PropType<T[K]>;
13
+ required: true;
14
+ };
15
+ };
@@ -1,4 +1,5 @@
1
1
  import { Ref } from 'vue';
2
+ import { FullscreenResult } from './utils/fullscreenManager';
2
3
 
3
4
  export declare enum FillMode {
4
5
  CONTAIN = "contain",
@@ -9,12 +10,13 @@ export interface PlayerControlState {
9
10
  isPlaying: Ref<boolean>;
10
11
  currentFillMode: Ref<FillMode>;
11
12
  isFullscreen: Ref<boolean>;
13
+ isLandscapeStyleMode: Ref<boolean>;
12
14
  isPictureInPicture: Ref<boolean>;
13
15
  currentVolume: Ref<number>;
14
16
  resume: () => Promise<boolean>;
15
17
  pause: () => Promise<boolean>;
16
- requestFullscreen: () => Promise<boolean>;
17
- exitFullscreen: () => Promise<boolean>;
18
+ requestFullscreen: () => Promise<FullscreenResult>;
19
+ exitFullscreen: () => Promise<FullscreenResult>;
18
20
  requestPictureInPicture: () => Promise<boolean>;
19
21
  exitPictureInPicture: () => Promise<boolean>;
20
22
  setVolume: (volume: number) => Promise<boolean>;
@@ -4,7 +4,7 @@ import { useLiveState } from "../../../states/LiveState/index.js";
4
4
  import { useLiveSeatState } from "../../../states/LiveSeatState/index.js";
5
5
  import { TRTCCloud } from "@tencentcloud/tuiroom-engine-js";
6
6
  import { getDeviceType, getCurrentOrientation, shouldRotateToLandscapeForFullscreen, hadLandscapeRotationToUndo } from "./utils/deviceDetection.js";
7
- import { OrientationManager, StyleManager, FullscreenManager, FullscreenMode } from "./utils/fullscreenManager.js";
7
+ import { OrientationManager, StyleManager, FullscreenMode, FullscreenManager } from "./utils/fullscreenManager.js";
8
8
  import { EventListenerManager, DOMElementGetter } from "./utils/domHelpers.js";
9
9
  import { LiveStatus } from "../../../types/live.js";
10
10
  var FillMode = /* @__PURE__ */ ((FillMode2) => {
@@ -19,12 +19,13 @@ const currentFillMode = ref(
19
19
  /* CONTAIN */
20
20
  );
21
21
  const isFullscreen = ref(false);
22
+ const isLandscapeStyleMode = ref(false);
22
23
  const isPictureInPicture = ref(false);
23
24
  const currentVolume = ref(1);
25
+ const roomEngine = useRoomEngine();
24
26
  function usePlayerControlState() {
25
27
  const { localLiveStatus } = useLiveState();
26
28
  const { canvas } = useLiveSeatState();
27
- const roomEngine = useRoomEngine();
28
29
  const eventManager = new EventListenerManager();
29
30
  const orientationListenerId = `player-control-${Date.now()}`;
30
31
  const isLandscapeStream = computed(
@@ -151,8 +152,9 @@ function usePlayerControlState() {
151
152
  } else {
152
153
  console.error("Fullscreen request failed:", result.error);
153
154
  }
154
- return result.success;
155
- }, "Request fullscreen", false);
155
+ isLandscapeStyleMode.value = result.shouldRotateToLandscape;
156
+ return result;
157
+ }, "Request fullscreen", { success: false, mode: FullscreenMode.CSS_SIMULATED, shouldRotateToLandscape: false });
156
158
  };
157
159
  const exitFullscreen = async () => {
158
160
  return withErrorHandling(async () => {
@@ -182,8 +184,9 @@ function usePlayerControlState() {
182
184
  } else {
183
185
  console.error("Fullscreen exit failed:", result.error);
184
186
  }
185
- return result.success;
186
- }, "Exit fullscreen", false);
187
+ isLandscapeStyleMode.value = false;
188
+ return result;
189
+ }, "Exit fullscreen", { success: false, mode: FullscreenMode.CSS_SIMULATED, shouldRotateToLandscape: false });
187
190
  };
188
191
  const requestPictureInPicture = async () => {
189
192
  return withErrorHandling(async () => {
@@ -260,6 +263,7 @@ function usePlayerControlState() {
260
263
  console.warn("Failed to unlock orientation during cleanup:", error);
261
264
  });
262
265
  }
266
+ isLandscapeStyleMode.value = false;
263
267
  console.log("Fullscreen exit style cleanup completed");
264
268
  } catch (error) {
265
269
  console.error("Fullscreen exit style cleanup failed:", error);
@@ -308,7 +312,7 @@ function usePlayerControlState() {
308
312
  const handleLeavePictureInPicture = () => {
309
313
  console.log("Left picture-in-picture mode");
310
314
  isPictureInPicture.value = false;
311
- resume();
315
+ setTimeout(resume, 300);
312
316
  };
313
317
  const handlePlay = () => {
314
318
  console.log("Video play event detected");
@@ -386,6 +390,7 @@ function usePlayerControlState() {
386
390
  isPlaying,
387
391
  currentFillMode,
388
392
  isFullscreen,
393
+ isLandscapeStyleMode,
389
394
  isPictureInPicture,
390
395
  currentVolume,
391
396
  // Methods
@@ -24,7 +24,7 @@ const _hoisted_3 = {
24
24
  const _sfc_main = /* @__PURE__ */ defineComponent({
25
25
  __name: "index",
26
26
  setup(__props) {
27
- const { isFullscreen } = usePlayerControlState();
27
+ const { isFullscreen, isLandscapeStyleMode } = usePlayerControlState();
28
28
  const { t } = useUIKit();
29
29
  const { seatList, canvas, startPlayStream, stopPlayStream } = useLiveSeatState();
30
30
  const { currentLive } = useLiveState();
@@ -239,7 +239,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
239
239
  fillMode.value = "fit";
240
240
  }
241
241
  handleStreamRegionSize();
242
- });
242
+ }, { deep: true });
243
243
  function handleStreamRegionSize() {
244
244
  if (!liveCoreViewContainerRef.value) {
245
245
  return;
@@ -344,14 +344,20 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
344
344
  to: "body",
345
345
  disabled: !unref(isMobile)
346
346
  }, [
347
- isShowPlayerControl.value ? (openBlock(), createBlock(PlayerControl, { key: 0 })) : createCommentVNode("", true)
347
+ isShowPlayerControl.value ? (openBlock(), createBlock(PlayerControl, {
348
+ key: 0,
349
+ isLandscapeStyleMode: unref(isLandscapeStyleMode)
350
+ }, null, 8, ["isLandscapeStyleMode"])) : createCommentVNode("", true)
348
351
  ], 8, ["disabled"])) : createCommentVNode("", true),
349
- isShowPlayerControl.value && unref(isFullscreen) ? (openBlock(), createBlock(PlayerControl, { key: 2 })) : createCommentVNode("", true)
352
+ isShowPlayerControl.value && unref(isFullscreen) ? (openBlock(), createBlock(PlayerControl, {
353
+ key: 2,
354
+ isLandscapeStyleMode: unref(isLandscapeStyleMode)
355
+ }, null, 8, ["isLandscapeStyleMode"])) : createCommentVNode("", true)
350
356
  ], 2);
351
357
  };
352
358
  }
353
359
  });
354
- const LiveCoreViewComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-84f2eab3"]]);
360
+ const LiveCoreViewComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-4d9101b9"]]);
355
361
  addI18n("en-US", { translation: resource });
356
362
  addI18n("zh-CN", { translation: resource$1 });
357
363
  export {
@@ -232,8 +232,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
232
232
  streamViewUI: withCtx((slotProps) => [
233
233
  renderSlot(_ctx.$slots, "streamViewUI", mergeProps({ ref_for: true }, slotProps), void 0, true)
234
234
  ]),
235
- _: 2
236
- }, 1032, ["userInfo", "stream-type", "style"]);
235
+ _: 3
236
+ }, 8, ["userInfo", "stream-type", "style"]);
237
237
  }), 128))
238
238
  ], 4)
239
239
  ], 512);
@@ -221,8 +221,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
221
221
  streamViewUI: withCtx((slotProps) => [
222
222
  renderSlot(_ctx.$slots, "streamViewUI", mergeProps({ ref_for: true }, slotProps), void 0, true)
223
223
  ]),
224
- _: 2
225
- }, 1032, ["userInfo", "stream-type", "style"]);
224
+ _: 3
225
+ }, 8, ["userInfo", "stream-type", "style"]);
226
226
  }), 128))
227
227
  ], 36)
228
228
  ], 2);
@@ -195,8 +195,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
195
195
  streamViewUI: withCtx((slotProps) => [
196
196
  renderSlot(_ctx.$slots, "streamViewUI", mergeProps({ ref_for: true }, slotProps), void 0, true)
197
197
  ]),
198
- _: 2
199
- }, 1032, ["user-info", "stream-type", "streamInfo", "style", "streamPlayMode", "streamPlayQuality"]);
198
+ _: 3
199
+ }, 8, ["user-info", "stream-type", "streamInfo", "style", "streamPlayMode", "streamPlayQuality"]);
200
200
  }), 128))
201
201
  ], 36)
202
202
  ], 2);
@@ -9877,7 +9877,7 @@ to {
9877
9877
  text-overflow: ellipsis;
9878
9878
  white-space: nowrap;
9879
9879
  overflow: hidden;
9880
- }.audio-control[data-v-81e72abd] {
9880
+ }.audio-control[data-v-22bb0b88] {
9881
9881
  --volume-control-primary: rgb(255, 255, 255);
9882
9882
  --volume-control-primary-hover: rgba(255, 255, 255, 0.1);
9883
9883
  --volume-control-background: rgba(0, 0, 0, 0.8);
@@ -9888,14 +9888,14 @@ to {
9888
9888
  display: flex;
9889
9889
  align-items: center;
9890
9890
  }
9891
- .volume-btn[data-v-81e72abd] {
9891
+ .volume-btn[data-v-22bb0b88] {
9892
9892
  width: 100%;
9893
9893
  height: 100%;
9894
9894
  flex-shrink: 0;
9895
9895
  cursor: pointer;
9896
9896
  transition: background-color 0.2s ease;
9897
9897
  }
9898
- .volume-slider-container[data-v-81e72abd] {
9898
+ .volume-slider-container[data-v-22bb0b88] {
9899
9899
  position: absolute;
9900
9900
  bottom: 100%;
9901
9901
  left: 50%;
@@ -9903,7 +9903,7 @@ to {
9903
9903
  margin-bottom: 12px;
9904
9904
  z-index: 100;
9905
9905
  }
9906
- .volume-slider-wrapper[data-v-81e72abd] {
9906
+ .volume-slider-wrapper[data-v-22bb0b88] {
9907
9907
  position: relative;
9908
9908
  display: flex;
9909
9909
  flex-direction: column;
@@ -9921,14 +9921,14 @@ to {
9921
9921
  -ms-user-select: none;
9922
9922
  }
9923
9923
  @media (hover: none) and (pointer: coarse) {
9924
- .volume-slider-wrapper[data-v-81e72abd] {
9924
+ .volume-slider-wrapper[data-v-22bb0b88] {
9925
9925
  cursor: grab;
9926
9926
  }
9927
- .volume-slider-wrapper[data-v-81e72abd]:active {
9927
+ .volume-slider-wrapper[data-v-22bb0b88]:active {
9928
9928
  cursor: grabbing;
9929
9929
  }
9930
9930
  }
9931
- .volume-slider-wrapper-inner[data-v-81e72abd] {
9931
+ .volume-slider-wrapper-inner[data-v-22bb0b88] {
9932
9932
  position: relative;
9933
9933
  width: 20px;
9934
9934
  height: 80px;
@@ -9937,7 +9937,7 @@ to {
9937
9937
  justify-content: center;
9938
9938
  margin-top: 12px;
9939
9939
  }
9940
- .custom-volume-slider[data-v-81e72abd] {
9940
+ .custom-volume-slider[data-v-22bb0b88] {
9941
9941
  position: relative;
9942
9942
  width: 4px;
9943
9943
  height: 80px;
@@ -9945,7 +9945,7 @@ to {
9945
9945
  z-index: 2;
9946
9946
  margin: 0;
9947
9947
  }
9948
- .slider-track[data-v-81e72abd] {
9948
+ .slider-track[data-v-22bb0b88] {
9949
9949
  position: absolute;
9950
9950
  top: 0;
9951
9951
  left: 0;
@@ -9955,7 +9955,7 @@ to {
9955
9955
  background: rgba(255, 255, 255, 0.2);
9956
9956
  border: none;
9957
9957
  }
9958
- .slider-progress[data-v-81e72abd] {
9958
+ .slider-progress[data-v-22bb0b88] {
9959
9959
  position: absolute;
9960
9960
  bottom: 0;
9961
9961
  left: 0;
@@ -9963,7 +9963,7 @@ to {
9963
9963
  background: #ffffff;
9964
9964
  border-radius: 2px;
9965
9965
  }
9966
- .slider-thumb[data-v-81e72abd] {
9966
+ .slider-thumb[data-v-22bb0b88] {
9967
9967
  position: absolute;
9968
9968
  left: 50%;
9969
9969
  transform: translateX(-50%);
@@ -9977,17 +9977,17 @@ to {
9977
9977
  z-index: 3;
9978
9978
  cursor: grab;
9979
9979
  }
9980
- .slider-thumb.no-transition[data-v-81e72abd] {
9980
+ .slider-thumb.no-transition[data-v-22bb0b88] {
9981
9981
  transition: none;
9982
9982
  }
9983
- .slider-thumb[data-v-81e72abd]:active {
9983
+ .slider-thumb[data-v-22bb0b88]:active {
9984
9984
  cursor: grabbing;
9985
9985
  transform: translateX(-50%) scale(1.1);
9986
9986
  }
9987
- .slider-thumb[data-v-81e72abd]:hover {
9987
+ .slider-thumb[data-v-22bb0b88]:hover {
9988
9988
  transform: translateX(-50%) scale(1.1);
9989
9989
  }
9990
- .volume-value[data-v-81e72abd] {
9990
+ .volume-value[data-v-22bb0b88] {
9991
9991
  color: var(--volume-control-primary);
9992
9992
  font-size: 12px;
9993
9993
  font-weight: 500;
@@ -9998,53 +9998,85 @@ to {
9998
9998
  pointer-events: none;
9999
9999
  }
10000
10000
  @media (hover: none) and (pointer: coarse) {
10001
- .volume-slider-wrapper[data-v-81e72abd] {
10001
+ .volume-slider-wrapper[data-v-22bb0b88] {
10002
10002
  padding: 16px 12px;
10003
10003
  }
10004
- .volume-slider-wrapper[data-v-81e72abd]:active {
10004
+ .volume-slider-wrapper[data-v-22bb0b88]:active {
10005
10005
  background: var(--volume-control-background-light);
10006
10006
  transform: scale(0.98);
10007
10007
  transition: all 0.1s ease;
10008
10008
  }
10009
- .volume-slider-wrapper-inner[data-v-81e72abd] {
10009
+ .volume-slider-wrapper-inner[data-v-22bb0b88] {
10010
10010
  height: 100px;
10011
10011
  }
10012
- }.pc-mode[data-v-cb28a3fe] {
10012
+ }.playback-controls[data-v-28ed6eb9] {
10013
+ background: #000000;
10014
+ padding: 12px 0;
10015
+ display: flex;
10016
+ width: calc(100% + 1px);
10017
+ align-items: center;
10018
+ box-sizing: border-box;
10019
+ }
10020
+ .pc-mode[data-v-28ed6eb9] {
10013
10021
  position: absolute;
10014
10022
  bottom: 0;
10015
10023
  left: 0;
10016
10024
  right: 0;
10017
10025
  z-index: 10;
10018
10026
  }
10019
- .mobile-mode[data-v-cb28a3fe] {
10020
- position: fixed !important;
10027
+ .mobile-mode[data-v-28ed6eb9] {
10028
+ position: fixed;
10021
10029
  bottom: 0;
10022
10030
  left: 0;
10023
10031
  right: 0;
10024
10032
  z-index: 999999;
10025
10033
  pointer-events: auto;
10026
10034
  }
10027
- .playback-controls[data-v-cb28a3fe] {
10028
- background: #000000;
10029
- padding: 12px 0;
10030
- display: flex;
10031
- width: calc(100% + 1px);
10032
- align-items: center;
10033
- box-sizing: border-box;
10035
+ @media screen and (orientation: portrait) {
10036
+ .mobile-landscape-mode[data-v-28ed6eb9] {
10037
+ position: fixed;
10038
+ bottom: unset;
10039
+ transform: rotate(90deg);
10040
+ transform-origin: left bottom;
10041
+ top: -60px;
10042
+ bottom: unset;
10043
+ width: 100vh;
10044
+ padding-right: 16px;
10045
+ }
10046
+ .mobile-landscape-mode.player-control-enter-active[data-v-28ed6eb9], .mobile-landscape-mode.player-control-leave-active[data-v-28ed6eb9] {
10047
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
10048
+ will-change: transform, opacity;
10049
+ }
10050
+ .mobile-landscape-mode.player-control-enter-from[data-v-28ed6eb9] {
10051
+ opacity: 0;
10052
+ transform: rotate(90deg) translateY(60px);
10053
+ }
10054
+ .mobile-landscape-mode.player-control-enter-to[data-v-28ed6eb9] {
10055
+ opacity: 1;
10056
+ transform: rotate(90deg) translateY(0);
10057
+ }
10058
+ .mobile-landscape-mode.player-control-leave-from[data-v-28ed6eb9] {
10059
+ opacity: 1;
10060
+ transform: rotate(90deg) translateY(0);
10061
+ }
10062
+ .mobile-landscape-mode.player-control-leave-to[data-v-28ed6eb9] {
10063
+ opacity: 0;
10064
+ transform: rotate(90deg) translateY(60px);
10034
10065
  }
10035
- .control-buttons[data-v-cb28a3fe] {
10066
+ }
10067
+ .control-buttons[data-v-28ed6eb9] {
10036
10068
  display: flex;
10037
10069
  align-items: center;
10038
10070
  justify-content: space-around;
10039
10071
  width: 100%;
10040
10072
  pointer-events: all;
10041
10073
  }
10042
- .center-controls[data-v-cb28a3fe] {
10074
+ .center-controls[data-v-28ed6eb9] {
10043
10075
  display: flex;
10044
10076
  align-items: center;
10045
10077
  gap: 16px;
10046
10078
  }
10047
- .control-btn[data-v-cb28a3fe] {
10079
+ .control-btn[data-v-28ed6eb9] {
10048
10080
  background: transparent;
10049
10081
  border: none;
10050
10082
  border-radius: 50%;
@@ -10054,63 +10086,62 @@ to {
10054
10086
  align-items: center;
10055
10087
  justify-content: center;
10056
10088
  cursor: pointer;
10057
- transition: all 0.2s ease;
10058
10089
  color: white;
10059
10090
  }
10060
- .control-btn[data-v-cb28a3fe]:hover {
10091
+ .control-btn[data-v-28ed6eb9]:hover {
10061
10092
  background: rgba(255, 255, 255, 0.1);
10062
10093
  }
10063
- .control-btn[data-v-cb28a3fe]:active {
10094
+ .control-btn[data-v-28ed6eb9]:active {
10064
10095
  transform: scale(0.95);
10065
10096
  }
10066
- .control-btn .btn-icon[data-v-cb28a3fe] {
10097
+ .control-btn .btn-icon[data-v-28ed6eb9] {
10067
10098
  width: 20px;
10068
10099
  height: 20px;
10069
10100
  fill: currentColor;
10070
10101
  }
10071
- .play-pause-btn .tui-icon[data-v-cb28a3fe] {
10102
+ .play-pause-btn .tui-icon[data-v-28ed6eb9] {
10072
10103
  transform: scale(1.5);
10073
10104
  }
10074
- .audio-control-btn[data-v-cb28a3fe]:active {
10105
+ .audio-control-btn[data-v-28ed6eb9]:active {
10075
10106
  transform: unset;
10076
10107
  }
10077
- .playback-time[data-v-cb28a3fe] {
10108
+ .playback-time[data-v-28ed6eb9] {
10078
10109
  color: white;
10079
10110
  font-size: 14px;
10080
10111
  font-weight: 500;
10081
10112
  margin-left: 16px;
10082
10113
  }
10083
- .right-controls[data-v-cb28a3fe] {
10114
+ .right-controls[data-v-28ed6eb9] {
10084
10115
  display: flex;
10085
10116
  align-items: center;
10086
10117
  gap: 16px;
10087
10118
  }
10088
- .fullscreen-btn .btn-icon[data-v-cb28a3fe] {
10119
+ .fullscreen-btn .btn-icon[data-v-28ed6eb9] {
10089
10120
  width: 18px;
10090
10121
  height: 18px;
10091
10122
  }
10092
- .more-btn .btn-icon[data-v-cb28a3fe] {
10123
+ .more-btn .btn-icon[data-v-28ed6eb9] {
10093
10124
  width: 18px;
10094
10125
  height: 18px;
10095
10126
  }
10096
- .player-control-enter-active[data-v-cb28a3fe],
10097
- .player-control-leave-active[data-v-cb28a3fe] {
10127
+ .player-control-enter-active[data-v-28ed6eb9],
10128
+ .player-control-leave-active[data-v-28ed6eb9] {
10098
10129
  transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
10099
10130
  will-change: transform, opacity;
10100
10131
  }
10101
- .player-control-enter-from[data-v-cb28a3fe] {
10132
+ .player-control-enter-from[data-v-28ed6eb9] {
10102
10133
  opacity: 0;
10103
10134
  transform: translateY(100%);
10104
10135
  }
10105
- .player-control-enter-to[data-v-cb28a3fe] {
10136
+ .player-control-enter-to[data-v-28ed6eb9] {
10106
10137
  opacity: 1;
10107
10138
  transform: translateY(0);
10108
10139
  }
10109
- .player-control-leave-from[data-v-cb28a3fe] {
10140
+ .player-control-leave-from[data-v-28ed6eb9] {
10110
10141
  opacity: 1;
10111
10142
  transform: translateY(0);
10112
10143
  }
10113
- .player-control-leave-to[data-v-cb28a3fe] {
10144
+ .player-control-leave-to[data-v-28ed6eb9] {
10114
10145
  opacity: 0;
10115
10146
  transform: translateY(100%);
10116
10147
  }.fullscreen-mode {
@@ -10149,17 +10180,17 @@ to {
10149
10180
  transform: rotate(90deg) !important;
10150
10181
  transform-origin: center center !important;
10151
10182
  }
10152
- }.live-core-view-container[data-v-84f2eab3] {
10183
+ }.live-core-view-container[data-v-4d9101b9] {
10153
10184
  width: 100%;
10154
10185
  height: 100%;
10155
10186
  display: flex;
10156
10187
  justify-content: center;
10157
10188
  overflow: hidden;
10158
10189
  }
10159
- .live-core-view-container.align-center[data-v-84f2eab3] {
10190
+ .live-core-view-container.align-center[data-v-4d9101b9] {
10160
10191
  align-items: center;
10161
10192
  }
10162
- .live-core-view-container .live-core-placeholder[data-v-84f2eab3] {
10193
+ .live-core-view-container .live-core-placeholder[data-v-4d9101b9] {
10163
10194
  position: absolute;
10164
10195
  top: 0;
10165
10196
  left: 0;
@@ -10169,26 +10200,26 @@ to {
10169
10200
  align-items: center;
10170
10201
  justify-content: center;
10171
10202
  }
10172
- .live-core-view-container .live-core-placeholder .placeholder-text[data-v-84f2eab3] {
10203
+ .live-core-view-container .live-core-placeholder .placeholder-text[data-v-4d9101b9] {
10173
10204
  color: var(--text-color-secondary, rgba(255, 255, 255, 0.55));
10174
10205
  font-size: 14px;
10175
10206
  font-style: normal;
10176
10207
  font-weight: 400;
10177
10208
  line-height: 22px;
10178
10209
  }
10179
- .live-core-view-container .live-core-view[data-v-84f2eab3] {
10210
+ .live-core-view-container .live-core-view[data-v-4d9101b9] {
10180
10211
  width: 100%;
10181
10212
  height: 100%;
10182
10213
  position: absolute;
10183
10214
  }
10184
- .live-core-view-container .live-core-view .stream-content[data-v-84f2eab3] {
10215
+ .live-core-view-container .live-core-view .stream-content[data-v-4d9101b9] {
10185
10216
  width: 100%;
10186
10217
  height: 100%;
10187
10218
  position: absolute;
10188
10219
  top: 0;
10189
10220
  left: 0;
10190
10221
  }
10191
- .live-core-view-container .live-core-view .live-core-ui[data-v-84f2eab3] {
10222
+ .live-core-view-container .live-core-view .live-core-ui[data-v-4d9101b9] {
10192
10223
  width: 100%;
10193
10224
  height: 100%;
10194
10225
  position: absolute;
@@ -10436,7 +10467,6 @@ to {
10436
10467
  width: 100%;
10437
10468
  overflow: auto;
10438
10469
  align-items: center;
10439
- scrollbar-width: none;
10440
10470
  }
10441
10471
  .live-list[data-v-96626ee9]::-webkit-scrollbar {
10442
10472
  width: 0px;
@@ -10454,6 +10484,9 @@ to {
10454
10484
  .live-list[data-v-96626ee9]::-webkit-scrollbar-thumb:hover {
10455
10485
  background: var(--uikit-color-gray-3);
10456
10486
  }
10487
+ .live-list[data-v-96626ee9] {
10488
+ scrollbar-width: none;
10489
+ }
10457
10490
  .live-list-items[data-v-96626ee9] {
10458
10491
  flex: 1;
10459
10492
  width: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuikit-atomicx-vue3",
3
- "version": "3.3.2-beta.1",
3
+ "version": "3.3.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -48,10 +48,10 @@
48
48
  "publish:github": "node scripts/publish-github.js"
49
49
  },
50
50
  "peerDependencies": {
51
- "@tencentcloud/chat": "^3.5.4",
51
+ "@tencentcloud/chat": "^3.5.8",
52
52
  "@tencentcloud/chat-uikit-engine": "~2.5.1",
53
53
  "@tencentcloud/tui-core": "latest",
54
- "@tencentcloud/tuiroom-engine-js": "~3.3.2-beta.1",
54
+ "@tencentcloud/tuiroom-engine-js": "~3.3.2",
55
55
  "@tencentcloud/uikit-base-component-vue3": "1.0.1",
56
56
  "vue": "^3.4.21"
57
57
  },
@@ -5,13 +5,13 @@
5
5
  <IconSpeakerOn size="20" v-else />
6
6
  </span>
7
7
  <div v-show="isVolumeSliderVisible" class="volume-slider-container">
8
- <div
8
+ <div
9
9
  class="volume-slider-wrapper"
10
10
  @mouseenter="handleVolumeSliderMouseEnter"
11
11
  @mouseleave="handleVolumeSliderMouseLeave"
12
12
  >
13
13
  <div class="volume-slider-wrapper-inner">
14
- <div
14
+ <div
15
15
  ref="volumeSliderElement"
16
16
  class="custom-volume-slider"
17
17
  @mousedown="handleSliderMouseDown"
@@ -19,11 +19,8 @@
19
19
  @click="handleVolumeSliderAreaClick"
20
20
  >
21
21
  <div class="slider-track">
22
- <div
23
- class="slider-progress"
24
- :style="{ height: `${volumePercentage}%` }"
25
- ></div>
26
- <div
22
+ <div class="slider-progress" :style="{ height: `${volumePercentage}%` }"></div>
23
+ <div
27
24
  class="slider-thumb"
28
25
  :class="{ 'no-transition': isDragging }"
29
26
  :style="{ bottom: `${volumePercentage}%` }"
@@ -38,7 +35,7 @@
38
35
  </template>
39
36
 
40
37
  <script setup lang="ts">
41
- import { computed, ref, onUnmounted } from 'vue';
38
+ import { computed, ref, onUnmounted, toRaw } from 'vue';
42
39
  import { IconSpeakerOn, IconSpeakerOff, useUIKit } from '@tencentcloud/uikit-base-component-vue3';
43
40
  import { isMobile } from '../../../utils';
44
41
 
@@ -64,7 +61,7 @@ const { t } = useUIKit();
64
61
  // Volume state - merged into single object
65
62
  const volumeState = ref({
66
63
  current: 1,
67
- previous: 1
64
+ previous: 1,
68
65
  });
69
66
 
70
67
  const isMuted = ref(false);
@@ -75,8 +72,8 @@ const volumeSliderAutoHideTimer = ref<number | null>(null);
75
72
 
76
73
  // Auto-hide delay for different platforms
77
74
  const AUTO_HIDE_DELAY = {
78
- PC: 500, // 0.5 seconds for PC
79
- MOBILE: 3000 // 3 seconds for mobile
75
+ PC: 500, // 0.5 seconds for PC
76
+ MOBILE: 3000, // 3 seconds for mobile
80
77
  };
81
78
 
82
79
  // Simplified computed property - directly use isVolumeSliderVisible
@@ -93,26 +90,25 @@ const iconSizeStyle = computed(() => ({
93
90
  }));
94
91
 
95
92
  const updateVolume = (newVolume: number) => {
93
+ volumeState.value.previous = toRaw(volumeState.value.current);
96
94
  volumeState.value.current = newVolume;
97
- volumeState.value.previous = newVolume;
98
95
  isMuted.value = newVolume === 0;
99
96
  emit('volume-change', newVolume);
100
97
  };
101
98
 
102
99
  const toggleMute = () => {
103
100
  if (isMuted.value) {
104
- // Unmute
105
101
  isMuted.value = false;
106
102
  volumeState.value.current = volumeState.value.previous || 0.2;
107
103
  emit('muted-change', false);
104
+ emit('volume-change', volumeState.value.current);
108
105
  } else {
109
- // Mute
110
106
  isMuted.value = true;
111
107
  volumeState.value.previous = volumeState.value.current;
112
108
  volumeState.value.current = 0;
113
109
  emit('muted-change', true);
110
+ emit('volume-change', volumeState.value.current);
114
111
  }
115
- updateVolume(volumeState.value.current);
116
112
  };
117
113
 
118
114
  const handleVolumeIconClick = () => {
@@ -124,7 +120,7 @@ const handleVolumeIconClick = () => {
124
120
  if (isMobile) {
125
121
  // On mobile: toggle volume slider visibility
126
122
  isVolumeSliderVisible.value = !isVolumeSliderVisible.value;
127
-
123
+
128
124
  // Start auto-hide timer when showing volume slider
129
125
  if (isVolumeSliderVisible.value) {
130
126
  startVolumeSliderAutoHideTimer();
@@ -157,10 +153,10 @@ const stopVolumeSliderAutoHideTimer = () => {
157
153
 
158
154
  const handleMouseEnter = () => {
159
155
  if (props.enableVolumeControl === false) return;
160
-
156
+
161
157
  // Only handle mouse events on PC
162
158
  if (isMobile) return;
163
-
159
+
164
160
  // On PC, show volume slider and start auto-hide timer
165
161
  isVolumeSliderVisible.value = true;
166
162
  startVolumeSliderAutoHideTimer();
@@ -168,10 +164,8 @@ const handleMouseEnter = () => {
168
164
 
169
165
  const handleMouseLeave = () => {
170
166
  if (props.enableVolumeControl === false) return;
171
-
172
167
  // Only handle mouse events on PC
173
168
  if (isMobile) return;
174
-
175
169
  // On PC, start auto-hide timer when mouse leaves icon area
176
170
  // But don't start if currently dragging
177
171
  if (!isDragging.value) {
@@ -183,7 +177,7 @@ const calculateVolumeFromPosition = (clientY: number, target: HTMLElement): numb
183
177
  const rect = target.getBoundingClientRect();
184
178
  const clickY = clientY - rect.top;
185
179
  const height = rect.height;
186
- return Math.max(0, Math.min(1, 1 - (clickY / height)));
180
+ return Math.max(0, Math.min(1, 1 - clickY / height));
187
181
  };
188
182
 
189
183
  const addGlobalEventListeners = () => {
@@ -202,63 +196,53 @@ const removeGlobalEventListeners = () => {
202
196
 
203
197
  const startDragging = () => {
204
198
  isDragging.value = true;
205
-
206
199
  // Stop auto-hide timer when dragging starts
207
200
  if (props.enableVolumeControl) {
208
201
  stopVolumeSliderAutoHideTimer();
209
202
  }
210
-
211
203
  addGlobalEventListeners();
212
204
  };
213
205
 
214
206
  const handleSliderMove = (event: MouseEvent | TouchEvent) => {
215
207
  if (!isDragging.value) return;
216
-
217
208
  event.preventDefault();
218
-
219
209
  let clientY: number;
220
210
  if (event instanceof MouseEvent) {
221
211
  clientY = event.clientY;
222
212
  } else {
223
213
  clientY = event.touches[0].clientY;
224
214
  }
225
-
226
215
  const volumeValue = calculateVolumeFromPosition(clientY, volumeSliderElement.value as HTMLElement);
227
216
  updateVolume(volumeValue);
228
217
  };
229
218
 
230
219
  const handleSliderEnd = () => {
231
220
  isDragging.value = false;
232
-
233
221
  // Restart auto-hide timer when dragging ends
234
222
  if (props.enableVolumeControl && isVolumeSliderVisible.value) {
235
223
  startVolumeSliderAutoHideTimer();
236
224
  }
237
-
238
225
  removeGlobalEventListeners();
239
226
  };
240
227
 
241
228
  const handleSliderMouseDown = (event: MouseEvent) => {
242
229
  if (props.enableVolumeControl === false) return;
243
-
244
230
  startDragging();
245
231
  event.preventDefault();
246
232
  };
247
233
 
248
234
  const handleSliderTouchStart = (event: TouchEvent) => {
249
235
  if (props.enableVolumeControl === false) return;
250
-
251
236
  startDragging();
252
237
  event.preventDefault();
253
238
  };
254
239
 
255
240
  const handleVolumeSliderAreaClick = () => {
256
241
  if (props.enableVolumeControl === false) return;
257
-
258
242
  if (isMobile) {
259
243
  // On mobile, toggle volume slider visibility
260
244
  isVolumeSliderVisible.value = !isVolumeSliderVisible.value;
261
-
245
+
262
246
  // Start auto-hide timer when showing volume slider
263
247
  if (isVolumeSliderVisible.value) {
264
248
  startVolumeSliderAutoHideTimer();
@@ -270,20 +254,16 @@ const handleVolumeSliderAreaClick = () => {
270
254
 
271
255
  const handleVolumeSliderMouseEnter = () => {
272
256
  if (props.enableVolumeControl === false) return;
273
-
274
257
  // Only handle mouse events on PC
275
258
  if (isMobile) return;
276
-
277
259
  // On PC, stop auto-hide timer when mouse enters slider area
278
260
  stopVolumeSliderAutoHideTimer();
279
261
  };
280
262
 
281
263
  const handleVolumeSliderMouseLeave = () => {
282
264
  if (props.enableVolumeControl === false) return;
283
-
284
265
  // Only handle mouse events on PC
285
266
  if (isMobile) return;
286
-
287
267
  // On PC, start auto-hide timer when mouse leaves slider area
288
268
  // But don't start if currently dragging
289
269
  if (!isDragging.value) {
@@ -293,8 +273,6 @@ const handleVolumeSliderMouseLeave = () => {
293
273
 
294
274
  onUnmounted(() => {
295
275
  removeGlobalEventListeners();
296
-
297
- // Clean up timers
298
276
  if (volumeSliderAutoHideTimer.value) {
299
277
  clearTimeout(volumeSliderAutoHideTimer.value);
300
278
  }
@@ -351,7 +329,7 @@ onUnmounted(() => {
351
329
 
352
330
  @media (hover: none) and (pointer: coarse) {
353
331
  cursor: grab;
354
-
332
+
355
333
  &:active {
356
334
  cursor: grabbing;
357
335
  }
@@ -412,16 +390,16 @@ onUnmounted(() => {
412
390
  transition: transform 0.1s ease;
413
391
  z-index: 3;
414
392
  cursor: grab;
415
-
393
+
416
394
  &.no-transition {
417
395
  transition: none;
418
396
  }
419
-
397
+
420
398
  &:active {
421
399
  cursor: grabbing;
422
400
  transform: translateX(-50%) scale(1.1);
423
401
  }
424
-
402
+
425
403
  &:hover {
426
404
  transform: translateX(-50%) scale(1.1);
427
405
  }
@@ -441,14 +419,14 @@ onUnmounted(() => {
441
419
  @media (hover: none) and (pointer: coarse) {
442
420
  .volume-slider-wrapper {
443
421
  padding: 16px 12px;
444
-
422
+
445
423
  &:active {
446
424
  background: var(--volume-control-background-light);
447
425
  transform: scale(0.98);
448
426
  transition: all 0.1s ease;
449
427
  }
450
428
  }
451
-
429
+
452
430
  .volume-slider-wrapper-inner {
453
431
  height: 100px;
454
432
  }
@@ -3,7 +3,11 @@
3
3
  <div
4
4
  v-show="showControls"
5
5
  ref="playerControlRef"
6
- :class="['playback-controls', isMobile ? 'mobile-mode' : 'pc-mode']"
6
+ :class="[
7
+ 'playback-controls',
8
+ isMobile ? 'mobile-mode' : 'pc-mode',
9
+ { 'mobile-landscape-mode': props.isLandscapeStyleMode },
10
+ ]"
7
11
  >
8
12
  <div class="control-buttons">
9
13
  <span class="control-btn play-pause-btn" :title="isPlaying ? t('Pause') : t('Play')" @click="handlePlayPause">
@@ -43,7 +47,15 @@
43
47
 
44
48
  <script setup lang="ts">
45
49
  import { onMounted, ref, onBeforeUnmount } from 'vue';
46
- import { IconFullScreen, IconPictureInPicture, IconPause, IconPlay, useUIKit, TUIToast, TOAST_TYPE } from '@tencentcloud/uikit-base-component-vue3';
50
+ import {
51
+ IconFullScreen,
52
+ IconPictureInPicture,
53
+ IconPause,
54
+ IconPlay,
55
+ useUIKit,
56
+ TUIToast,
57
+ TOAST_TYPE,
58
+ } from '@tencentcloud/uikit-base-component-vue3';
47
59
  import { usePlayerControlState } from './PlayerControlState';
48
60
  import AudioControl from './AudioControl.vue';
49
61
  import { isMobile } from '../../../utils';
@@ -63,12 +75,14 @@ const {
63
75
  cleanup,
64
76
  } = usePlayerControlState();
65
77
 
78
+ const props = defineProps<{
79
+ isLandscapeStyleMode?: boolean;
80
+ }>();
81
+
66
82
  const { t } = useUIKit();
67
83
  const currentVolume = ref(1);
68
84
  const isMuted = ref(false);
69
-
70
85
  const playerControlRef = ref<HTMLElement>();
71
-
72
86
  const showControls = ref(false);
73
87
  const hideTimeout = ref<number | null>(null);
74
88
 
@@ -214,29 +228,29 @@ const handleScreenTouchStart = (event: TouchEvent) => {
214
228
  }
215
229
  };
216
230
 
217
- const handleScreenTouchMove = (event: TouchEvent) => {
231
+ const handleScreenTouchMove = (event: TouchEvent) => {
218
232
  if (playerControlRef.value && playerControlRef.value.contains(event.target as Node)) {
219
233
  stopAutoHideControl();
220
234
  return;
221
235
  }
222
- }
236
+ };
223
237
 
224
238
  const handleScreenTouchEnd = (event: TouchEvent) => {
225
239
  if (!touchStartCoords.value) {
226
240
  return;
227
241
  }
228
-
242
+
229
243
  const touchEnd = event.changedTouches[0];
230
244
  const distance = calculateTouchDistance(touchStartCoords.value, touchEnd);
231
-
245
+
232
246
  const MAX_CLICK_DISTANCE = 20;
233
247
  if (distance > MAX_CLICK_DISTANCE) {
234
248
  touchStartCoords.value = null;
235
249
  return;
236
250
  }
237
-
251
+
238
252
  const target = event.target as Node;
239
-
253
+
240
254
  if (isPlayerControlTarget(target)) {
241
255
  handlePlayerControlTouch();
242
256
  } else if (isLiveCoreViewTarget(target)) {
@@ -244,7 +258,7 @@ const handleScreenTouchEnd = (event: TouchEvent) => {
244
258
  } else {
245
259
  showControls.value = false;
246
260
  }
247
-
261
+
248
262
  touchStartCoords.value = null;
249
263
  };
250
264
 
@@ -291,6 +305,15 @@ onBeforeUnmount(() => {
291
305
  </script>
292
306
 
293
307
  <style scoped lang="scss">
308
+ .playback-controls {
309
+ background: #000000;
310
+ padding: 12px 0;
311
+ display: flex;
312
+ width: calc(100% + 1px); // Solve the problem of 1px deviation during absolute positioning
313
+ align-items: center;
314
+ box-sizing: border-box;
315
+ }
316
+
294
317
  .pc-mode {
295
318
  position: absolute;
296
319
  bottom: 0;
@@ -300,20 +323,53 @@ onBeforeUnmount(() => {
300
323
  }
301
324
 
302
325
  .mobile-mode {
303
- position: fixed !important;
326
+ position: fixed;
304
327
  bottom: 0;
305
328
  left: 0;
306
329
  right: 0;
307
330
  z-index: 999999;
308
331
  pointer-events: auto;
309
332
  }
310
- .playback-controls {
311
- background: #000000;
312
- padding: 12px 0;
313
- display: flex;
314
- width: calc(100% + 1px); // Solve the problem of 1px deviation during absolute positioning
315
- align-items: center;
316
- box-sizing: border-box;
333
+
334
+ @media screen and (orientation: portrait) {
335
+ .mobile-landscape-mode {
336
+ position: fixed;
337
+ bottom: unset;
338
+ transform: rotate(90deg);
339
+ transform-origin: left bottom;
340
+ top: -60px;
341
+ bottom: unset;
342
+ width: 100vh;
343
+ padding-right: 16px;
344
+ }
345
+
346
+ .mobile-landscape-mode {
347
+ &.player-control-enter-active,
348
+ &.player-control-leave-active {
349
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
350
+ will-change: transform, opacity;
351
+ }
352
+
353
+ &.player-control-enter-from {
354
+ opacity: 0;
355
+ transform: rotate(90deg) translateY(60px);
356
+ }
357
+
358
+ &.player-control-enter-to {
359
+ opacity: 1;
360
+ transform: rotate(90deg) translateY(0);
361
+ }
362
+
363
+ &.player-control-leave-from {
364
+ opacity: 1;
365
+ transform: rotate(90deg) translateY(0);
366
+ }
367
+
368
+ &.player-control-leave-to {
369
+ opacity: 0;
370
+ transform: rotate(90deg) translateY(60px);
371
+ }
372
+ }
317
373
  }
318
374
 
319
375
  .control-buttons {
@@ -340,7 +396,6 @@ onBeforeUnmount(() => {
340
396
  align-items: center;
341
397
  justify-content: center;
342
398
  cursor: pointer;
343
- transition: all 0.2s ease;
344
399
  color: white;
345
400
 
346
401
  &:hover {
@@ -37,6 +37,7 @@ export interface PlayerControlState {
37
37
  isPlaying: Ref<boolean>;
38
38
  currentFillMode: Ref<FillMode>;
39
39
  isFullscreen: Ref<boolean>;
40
+ isLandscapeStyleMode: Ref<boolean>;
40
41
  isPictureInPicture: Ref<boolean>;
41
42
  currentVolume: Ref<number>;
42
43
 
@@ -45,8 +46,8 @@ export interface PlayerControlState {
45
46
  pause: () => Promise<boolean>;
46
47
 
47
48
  // Fullscreen control methods
48
- requestFullscreen: () => Promise<boolean>;
49
- exitFullscreen: () => Promise<boolean>;
49
+ requestFullscreen: () => Promise<FullscreenResult>;
50
+ exitFullscreen: () => Promise<FullscreenResult>;
50
51
 
51
52
  // Picture-in-picture control methods
52
53
  requestPictureInPicture: () => Promise<boolean>;
@@ -64,8 +65,10 @@ export interface PlayerControlState {
64
65
  const isPlaying = ref(true);
65
66
  const currentFillMode = ref<FillMode>(FillMode.CONTAIN);
66
67
  const isFullscreen = ref(false);
68
+ const isLandscapeStyleMode = ref(false);
67
69
  const isPictureInPicture = ref(false);
68
70
  const currentVolume = ref(1.0); // Default volume is 1.0 (100%)
71
+ const roomEngine = useRoomEngine();
69
72
 
70
73
  /**
71
74
  * Player control state management hook
@@ -74,7 +77,6 @@ export function usePlayerControlState(): PlayerControlState {
74
77
  // Dependency injection
75
78
  const { localLiveStatus } = useLiveState();
76
79
  const { canvas } = useLiveSeatState();
77
- const roomEngine = useRoomEngine();
78
80
 
79
81
  // Event listener management
80
82
  const eventManager = new EventListenerManager();
@@ -211,7 +213,7 @@ export function usePlayerControlState(): PlayerControlState {
211
213
  /**
212
214
  * Fullscreen control methods
213
215
  */
214
- const requestFullscreen = async (): Promise<boolean> => {
216
+ const requestFullscreen = async (): Promise<FullscreenResult> => {
215
217
  return withErrorHandling(async () => {
216
218
  const elements = DOMElementGetter.getAllElements();
217
219
  const validation = DOMElementGetter.validateElements(elements);
@@ -248,12 +250,12 @@ export function usePlayerControlState(): PlayerControlState {
248
250
  } else {
249
251
  console.error('Fullscreen request failed:', result.error);
250
252
  }
251
-
252
- return result.success;
253
- }, 'Request fullscreen', false);
253
+ isLandscapeStyleMode.value = result.shouldRotateToLandscape;
254
+ return result;
255
+ }, 'Request fullscreen', { success: false, mode: FullscreenMode.CSS_SIMULATED, shouldRotateToLandscape: false });
254
256
  };
255
257
 
256
- const exitFullscreen = async (): Promise<boolean> => {
258
+ const exitFullscreen = async (): Promise<FullscreenResult> => {
257
259
  return withErrorHandling(async () => {
258
260
  const elements = DOMElementGetter.getAllElements();
259
261
  if (!elements.view) {
@@ -291,9 +293,9 @@ export function usePlayerControlState(): PlayerControlState {
291
293
  } else {
292
294
  console.error('Fullscreen exit failed:', result.error);
293
295
  }
294
-
295
- return result.success;
296
- }, 'Exit fullscreen', false);
296
+ isLandscapeStyleMode.value = false;
297
+ return result;
298
+ }, 'Exit fullscreen', { success: false, mode: FullscreenMode.CSS_SIMULATED, shouldRotateToLandscape: false });
297
299
  };
298
300
 
299
301
  /**
@@ -394,7 +396,8 @@ export function usePlayerControlState(): PlayerControlState {
394
396
  console.warn('Failed to unlock orientation during cleanup:', error);
395
397
  });
396
398
  }
397
-
399
+
400
+ isLandscapeStyleMode.value = false;
398
401
  console.log('Fullscreen exit style cleanup completed');
399
402
  } catch (error) {
400
403
  console.error('Fullscreen exit style cleanup failed:', error);
@@ -419,7 +422,6 @@ export function usePlayerControlState(): PlayerControlState {
419
422
 
420
423
  // Update state
421
424
  isFullscreen.value = isCurrentlyFullscreen;
422
-
423
425
  // If exiting fullscreen, need to cleanup styles
424
426
  if (wasFullscreen && !isCurrentlyFullscreen) {
425
427
  console.log('Detected passive fullscreen exit, executing style cleanup');
@@ -462,7 +464,7 @@ export function usePlayerControlState(): PlayerControlState {
462
464
  const handleLeavePictureInPicture = () => {
463
465
  console.log('Left picture-in-picture mode');
464
466
  isPictureInPicture.value = false;
465
- resume();
467
+ setTimeout(resume, 300);
466
468
  };
467
469
 
468
470
  // Video playback state change handlers
@@ -580,6 +582,7 @@ export function usePlayerControlState(): PlayerControlState {
580
582
  isPlaying,
581
583
  currentFillMode,
582
584
  isFullscreen,
585
+ isLandscapeStyleMode,
583
586
  isPictureInPicture,
584
587
  currentVolume,
585
588
 
@@ -46,9 +46,9 @@
46
46
  />
47
47
  </div>
48
48
  <Teleport to="body" v-if="!isFullscreen" :disabled="!isMobile">
49
- <PlayerControl v-if="isShowPlayerControl" />
49
+ <PlayerControl :isLandscapeStyleMode="isLandscapeStyleMode" v-if="isShowPlayerControl" />
50
50
  </Teleport>
51
- <PlayerControl v-if="isShowPlayerControl && isFullscreen" />
51
+ <PlayerControl :isLandscapeStyleMode="isLandscapeStyleMode" v-if="isShowPlayerControl && isFullscreen" />
52
52
  </div>
53
53
  </template>
54
54
 
@@ -65,7 +65,7 @@ import type { SeatInfo, SeatUserInfo } from '../../types';
65
65
  import { isMobile } from '../../utils';
66
66
  import { usePlayerControlState } from './PlayerControl';
67
67
 
68
- const { isFullscreen } = usePlayerControlState();
68
+ const { isFullscreen, isLandscapeStyleMode } = usePlayerControlState();
69
69
  const { t } = useUIKit();
70
70
  const { seatList, canvas, startPlayStream, stopPlayStream } = useLiveSeatState();
71
71
  const { currentLive } = useLiveState();
@@ -304,7 +304,7 @@ watch(() => [canvas.value, seatList.value], () => {
304
304
  fillMode.value = StreamFillMode.Fit;
305
305
  }
306
306
  handleStreamRegionSize();
307
- });
307
+ }, { deep: true });
308
308
 
309
309
  function handleStreamRegionSize() {
310
310
  if (!liveCoreViewContainerRef.value) {