|
@@ -54,7 +54,7 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
|
|
|
const totalFrames = useSelector(selectTotalFrames(viewportId));
|
|
const totalFrames = useSelector(selectTotalFrames(viewportId));
|
|
|
const playbackSpeed = useSelector(selectPlaybackSpeed(viewportId));
|
|
const playbackSpeed = useSelector(selectPlaybackSpeed(viewportId));
|
|
|
const isLoop = useSelector(selectIsLoop(viewportId));
|
|
const isLoop = useSelector(selectIsLoop(viewportId));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 保存viewport和renderingEngine引用
|
|
// 保存viewport和renderingEngine引用
|
|
|
const viewportRef = useRef<Types.IStackViewport | null>(null);
|
|
const viewportRef = useRef<Types.IStackViewport | null>(null);
|
|
|
const renderingEngineRef = useRef<Types.IRenderingEngine | null>(null);
|
|
const renderingEngineRef = useRef<Types.IRenderingEngine | null>(null);
|
|
@@ -63,27 +63,29 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 获取viewport实例
|
|
* 获取viewport实例
|
|
|
|
|
+ * 每次都重新获取,避免使用已销毁的viewport缓存引用
|
|
|
*/
|
|
*/
|
|
|
const getViewport = useCallback((): Types.IStackViewport | null => {
|
|
const getViewport = useCallback((): Types.IStackViewport | null => {
|
|
|
try {
|
|
try {
|
|
|
- if (!viewportRef.current || !renderingEngineRef.current) {
|
|
|
|
|
- const renderingEngine = cornerstone.getRenderingEngine(renderingEngineId);
|
|
|
|
|
- if (!renderingEngine) {
|
|
|
|
|
- console.warn(`[PlaybackController] Rendering engine not found: ${renderingEngineId}`);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const viewport = renderingEngine.getViewport(viewportId) as Types.IStackViewport;
|
|
|
|
|
- if (!viewport) {
|
|
|
|
|
- console.warn(`[PlaybackController] Viewport not found: ${viewportId}`);
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- viewportRef.current = viewport;
|
|
|
|
|
- renderingEngineRef.current = renderingEngine;
|
|
|
|
|
|
|
+ // 总是从renderingEngine重新获取最新的viewport
|
|
|
|
|
+ // 不使用缓存,避免viewport被销毁后仍使用旧引用的问题
|
|
|
|
|
+ const renderingEngine = cornerstone.getRenderingEngine(renderingEngineId);
|
|
|
|
|
+ if (!renderingEngine) {
|
|
|
|
|
+ console.warn(`[PlaybackController] Rendering engine not found: ${renderingEngineId}`);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const viewport = renderingEngine.getViewport(viewportId) as Types.IStackViewport;
|
|
|
|
|
+ if (!viewport) {
|
|
|
|
|
+ console.warn(`[PlaybackController] Viewport not found: ${viewportId}`);
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return viewportRef.current;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 更新缓存引用(供调试用)
|
|
|
|
|
+ viewportRef.current = viewport;
|
|
|
|
|
+ renderingEngineRef.current = renderingEngine;
|
|
|
|
|
+
|
|
|
|
|
+ return viewport;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(`[PlaybackController] Error getting viewport:`, error);
|
|
console.error(`[PlaybackController] Error getting viewport:`, error);
|
|
|
onPlaybackError?.(error as Error);
|
|
onPlaybackError?.(error as Error);
|
|
@@ -101,19 +103,19 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
|
|
|
try {
|
|
try {
|
|
|
// 确保帧索引在有效范围内
|
|
// 确保帧索引在有效范围内
|
|
|
const clampedFrame = Math.max(0, Math.min(frameIndex, totalFrames - 1));
|
|
const clampedFrame = Math.max(0, Math.min(frameIndex, totalFrames - 1));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 如果是多帧DICOM,使用setImageIdIndex
|
|
// 如果是多帧DICOM,使用setImageIdIndex
|
|
|
if (totalFrames > 1) {
|
|
if (totalFrames > 1) {
|
|
|
await viewport.setImageIdIndex(clampedFrame);
|
|
await viewport.setImageIdIndex(clampedFrame);
|
|
|
viewport.render();
|
|
viewport.render();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 更新Redux状态
|
|
// 更新Redux状态
|
|
|
if (clampedFrame !== currentFrame) {
|
|
if (clampedFrame !== currentFrame) {
|
|
|
dispatch(setCurrentFrame({ viewportId, frame: clampedFrame }));
|
|
dispatch(setCurrentFrame({ viewportId, frame: clampedFrame }));
|
|
|
onFrameChange?.(clampedFrame);
|
|
onFrameChange?.(clampedFrame);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
lastFrameRef.current = clampedFrame;
|
|
lastFrameRef.current = clampedFrame;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(`[PlaybackController] Error switching to frame ${frameIndex}:`, error);
|
|
console.error(`[PlaybackController] Error switching to frame ${frameIndex}:`, error);
|
|
@@ -130,17 +132,17 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const intervalMs = 1000 / playbackSpeed;
|
|
const intervalMs = 1000 / playbackSpeed;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
const playIntervalId = setInterval(() => {
|
|
const playIntervalId = setInterval(() => {
|
|
|
dispatch(nextFrame(viewportId));
|
|
dispatch(nextFrame(viewportId));
|
|
|
}, intervalMs);
|
|
}, intervalMs);
|
|
|
|
|
|
|
|
playIntervalRef.current = playIntervalId;
|
|
playIntervalRef.current = playIntervalId;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 保存定时器ID到Redux(用于清理)
|
|
// 保存定时器ID到Redux(用于清理)
|
|
|
- dispatch(setPlayIntervalId({
|
|
|
|
|
- viewportId,
|
|
|
|
|
- intervalId: playIntervalId as any
|
|
|
|
|
|
|
+ dispatch(setPlayIntervalId({
|
|
|
|
|
+ viewportId,
|
|
|
|
|
+ intervalId: playIntervalId as any
|
|
|
}));
|
|
}));
|
|
|
|
|
|
|
|
console.log(`[PlaybackController] Started playback timer for ${viewportId}, speed: ${playbackSpeed}fps`);
|
|
console.log(`[PlaybackController] Started playback timer for ${viewportId}, speed: ${playbackSpeed}fps`);
|
|
@@ -226,7 +228,7 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
|
|
|
// 重置viewport引用,强制重新获取
|
|
// 重置viewport引用,强制重新获取
|
|
|
viewportRef.current = null;
|
|
viewportRef.current = null;
|
|
|
renderingEngineRef.current = null;
|
|
renderingEngineRef.current = null;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 预先获取viewport以验证可用性
|
|
// 预先获取viewport以验证可用性
|
|
|
getViewport();
|
|
getViewport();
|
|
|
}, [viewportId, renderingEngineId, getViewport]);
|
|
}, [viewportId, renderingEngineId, getViewport]);
|
|
@@ -242,7 +244,7 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
|
|
|
export const usePlaybackController = (viewportId: string) => {
|
|
export const usePlaybackController = (viewportId: string) => {
|
|
|
const dispatch = useDispatch();
|
|
const dispatch = useDispatch();
|
|
|
const playbackState = useSelector(selectViewportPlayback(viewportId));
|
|
const playbackState = useSelector(selectViewportPlayback(viewportId));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
// 状态
|
|
// 状态
|
|
|
playbackState,
|
|
playbackState,
|
|
@@ -250,31 +252,31 @@ export const usePlaybackController = (viewportId: string) => {
|
|
|
currentFrame: playbackState?.currentFrame || 0,
|
|
currentFrame: playbackState?.currentFrame || 0,
|
|
|
totalFrames: playbackState?.totalFrames || 1,
|
|
totalFrames: playbackState?.totalFrames || 1,
|
|
|
isMultiFrame: playbackState?.isMultiFrame || false,
|
|
isMultiFrame: playbackState?.isMultiFrame || false,
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 控制方法
|
|
// 控制方法
|
|
|
togglePlayback: () => {
|
|
togglePlayback: () => {
|
|
|
- dispatch(setPlaybackState({
|
|
|
|
|
- viewportId,
|
|
|
|
|
- isPlaying: !playbackState?.isPlaying
|
|
|
|
|
|
|
+ dispatch(setPlaybackState({
|
|
|
|
|
+ viewportId,
|
|
|
|
|
+ isPlaying: !playbackState?.isPlaying
|
|
|
}));
|
|
}));
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
play: () => {
|
|
play: () => {
|
|
|
dispatch(setPlaybackState({ viewportId, isPlaying: true }));
|
|
dispatch(setPlaybackState({ viewportId, isPlaying: true }));
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
pause: () => {
|
|
pause: () => {
|
|
|
dispatch(setPlaybackState({ viewportId, isPlaying: false }));
|
|
dispatch(setPlaybackState({ viewportId, isPlaying: false }));
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
goToFrame: (frame: number) => {
|
|
goToFrame: (frame: number) => {
|
|
|
dispatch(setCurrentFrame({ viewportId, frame }));
|
|
dispatch(setCurrentFrame({ viewportId, frame }));
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
nextFrame: () => {
|
|
nextFrame: () => {
|
|
|
dispatch(nextFrame(viewportId));
|
|
dispatch(nextFrame(viewportId));
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
previousFrame: () => {
|
|
previousFrame: () => {
|
|
|
const prevFrame = Math.max(0, (playbackState?.currentFrame || 0) - 1);
|
|
const prevFrame = Math.max(0, (playbackState?.currentFrame || 0) - 1);
|
|
|
dispatch(setCurrentFrame({ viewportId, frame: prevFrame }));
|
|
dispatch(setCurrentFrame({ viewportId, frame: prevFrame }));
|