여러 슬라이드의 비머 프레젠테이션에 사운드 포함

여러 슬라이드의 비머 프레젠테이션에 사운드 포함

비머 프리젠테이션에 사운드를 포함하고 해당 사운드가 특정 슬라이드에서 시작되어 여러 오버레이 또는 여러 슬라이드에 걸쳐 지속되기를 원합니다. 나는 media9or multimedia(문서에 따르면 둘 다 작동해야 함)을 사용하여 그것을 얻으려고 노력했습니다. 그러나 두 경우 모두(아래 MWE 참조) 사운드는 시작해야 할 때 시작되지만 항목 2가 표시되면 중지됩니다. 두 개 이상의 슬라이드에 대해 사운드가 지속되도록 하려면 어떻게 해야 합니까(패키지 중 하나 media9또는 multimedia어느 것인지는 상관하지 않음)?

다음 에 대한 MWE media9:

\documentclass{beamer}
\usepackage{media9}
\begin{document}
\begin{frame}{1}
1
\end{frame}

\begin{frame}{2}
\includemedia[label=my_sound, width=1ex, height=1ex, transparent, activate=pageopen, deactivate=onclick, addresource=./Sounds/MITM.mp3,
flashvars={
source=./Sounds/MITM.mp3
&autoPlay=true
},]{}{APlayer.swf}
\begin{itemize}
\item<2-> 2
\item<3-> 3
\end{itemize}
\end{frame}

\begin{frame}{3}
3
\end{frame}

\begin{frame}{4}
4
\end{frame}
\end{document}

MWE for multimedia: (여기에서도 autostart옵션이 작동하지 않으며 수동으로 사운드를 시작해야 하며 옵션이 없으면 사운드가 전혀 들리지 않습니다 inlinesound.)

\documentclass{beamer}
\usepackage{multimedia}
\begin{document}
\begin{frame}{1}
1
\end{frame}

\begin{frame}{2}
\sound[autostart, inlinesound, bitspersample=16, channels=2, encoding=Signed, samplingrate=48000]{A}{./Sounds/MITM.au}
\begin{itemize}
\item<2-> 2
\item<3-> 3
\end{itemize}
\end{frame}

\begin{frame}{3}
3
\end{frame}

\begin{frame}{4}
4
\end{frame}
\end{document}

이것이 중요한 경우: Windows 11에서 Adobe Reader(최신 버전)를 사용합니다.

답변1

내가 아는 한, 현재 일반적으로 사용되는 PDF 뷰어 중 어느 것도 여러 슬라이드에서 사운드 재생을 지원하지 않습니다. 그러나 이를 지원할 수 있는 자체 뷰어를 만드는 것은 매우 간단합니다.

다음을 이름이 지정된 파일 viewer.html(또는 <anything>.html원하는 경우)에 저장합니다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="
            width=device-width,
            initial-scale=1,
            maximum-scale=1
        ">
        <style>
            /* Make the viewer take up the full screen */
            * {
                margin: 0;
                padding: 0;
                border: 0;
                outline: 0;
            }

            #viewer-container {
                overflow: clip;
                position: absolute;
                width: 100%;
                height: 100%;
            }

            /* Center the page */
            .page {
                margin: 0 auto 0;
            }
        </style>

        <script type="module">
            import "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.1.392/pdf.mjs"
            import "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.1.392/pdf_viewer.mjs"
            pdfjsLib.GlobalWorkerOptions.workerSrc =
            "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.1.392/pdf.worker.mjs"

            const bus = new pdfjsViewer.EventBus()
            const container = document.getElementById("viewer-container")

            const viewer = new pdfjsViewer.PDFSinglePageViewer({
                container,
                eventBus: bus,
                textLayerMode: 0,
                annotationMode: 0,
            })

            bus.on("pagechanging", () => {
                viewer.currentScaleValue = "page-fit"
            })

            const file_element = document.getElementById("file")

            async function open(event) {
                let file
                if (event instanceof window.DragEvent) {
                    event.dataTransfer.dropEffect = "copy"
                    file = event.dataTransfer.files[0]
                } else if (event.target instanceof window.HTMLInputElement) {
                    file = event.target.files[0]
                }
                file_element.remove()

                const doc = await pdfjsLib.getDocument(
                    await file.arrayBuffer()
                ).promise

                viewer.setDocument(doc)

                const attatchments = await doc.getAttachments()

                let audio

                async function sound() {
                    viewer.currentScaleValue = "page-fit"

                    const page = await doc.getPage(viewer.currentPageNumber)
                    const annotations = await page.getAnnotations()

                    for (const annotation of annotations) {
                        const directive = annotation?.contentsObj?.str
                        if (!directive) continue

                        const [cmd, file] = directive.split(" ")

                        if (["play", "stop"].includes(cmd) && audio) {
                            audio.pause()
                            audio = null
                        }

                        if (cmd === "play") {
                            const blob = new Blob([attatchments[file].content])
                            audio = new Audio(URL.createObjectURL(blob))
                            audio.play()
                        }
                    }
                }

                function move(direction) {
                    if (!document.fullscreenElement) {
                        document.documentElement.requestFullscreen()
                    } else {
                        if (direction === "next") {
                            viewer.nextPage()
                        } else if (direction === "previous") {
                            viewer.previousPage()
                        }
                    }
                    sound()
                }

                document.addEventListener("keydown", (event) => {
                    switch (event.key) {
                        case "ArrowRight":
                        case "ArrowDown":
                        case " ":
                        case "PageDown":
                        case "Enter":
                            event.preventDefault()
                            move("next")
                            break
                        case "ArrowLeft":
                        case "ArrowUp":
                        case "PageUp":
                        case "Backspace":
                            event.preventDefault()
                            move("previous")
                            break
                    }
                })

                document.addEventListener("click", (event) => {
                    event.preventDefault()
                    switch (event.button) {
                        case 0:
                            event.preventDefault()
                            move("next")
                            break
                        case 2:
                            event.preventDefault()
                            move("previous")
                            break
                    }
                })
            }

            file_element.addEventListener("change", open)
            document.addEventListener("drop", open)
            document.addEventListener("dragover", event => event.preventDefault())
        </script>
    </head>
    <body>
        <input type="file" id="file" accept="application/pdf">
        <div id="viewer-container">
            <div id="viewer"></div>
        </div>
    </body>
</html>

그런 다음 pdfLaTeX 또는 LuaLaTeX를 사용하여 다음 문서를 컴파일합니다.

\documentclass{beamer}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Load required packages %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Needed to embed the audio files
\usepackage{embedfile}

%% Needed for \pdfannot to work with LuaTeX
\ifdefined\directlua
    \usepackage{luatex85}
\fi

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Variable Declarations %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\ExplSyntaxOn
    \seq_new:N \g__example_files_seq %% Store all used files
    \tl_new:N \l__example_lastfile_tl %% The most recent file loaded
    \tl_new:N \g__example_currentfile_tl %% The currently playing file


%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Command Definitions %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%

    %% Embeds a file if it isn't already embedded, and stores its attachment
    %% name in "\l__example_lastfile_tl".
    \cs_new_protected:Nn \__example_add_file:n {
        \tl_set:Ne \l__example_lastfile_tl { \str_mdfive_hash:n { #1 } }

        \seq_if_in:NVF \g__example_files_seq \l__example_lastfile_tl {
            \seq_gput_right:NV \g__example_files_seq \l__example_lastfile_tl
            \embedfile [ filespec = \l__example_lastfile_tl ] { #1 }
        }
    }

    %% Writes to the PDF the given command and parameter.
    \cs_new_protected:Nn \__example_pdfcmd:nn {
        \pdfannot width 0pt~ height 0pt~ depth 0pt~ {%
            /Subtype~ /Text
            /Contents~ (#1~ #2)
            /F~ 1
        }
    }
    \cs_generate_variant:Nn \__example_pdfcmd:nn { nV }

    %% Plays the given audio file.
    \cs_new_protected:Nn \__example_play:n {
        \__example_add_file:n { #1 }
        \tl_if_eq:NNF \g__example_currentfile_tl \l__example_lastfile_tl {
            \tl_gset:NV \g__example_currentfile_tl \l__example_lastfile_tl
            \__example_pdfcmd:nV { play } \l__example_lastfile_tl
        }
    }

    %% Stops the currently playing audio file.
    \cs_new_protected:Nn \__example_stop: {
        \__example_pdfcmd:nn { stop } { }
        \tl_gclear:N \g__example_currentfile_tl
    }

    %% Expose the commands publicly
    \cs_set_eq:NN \playaudio \__example_play:n
    \cs_set_eq:NN \stopaudio \__example_stop:
\ExplSyntaxOff


%%%%%%%%%%%%%%%%%%%%%
%%% Demonstration %%%
%%%%%%%%%%%%%%%%%%%%%

%% There are no audio files in $TEXMFDIST, so we need to lookup the
%% (system-dependent) path to a sample audio file.
\ExplSyntaxOn
    \sys_get_shell:nnN { kpsewhich~ --format=source~ bird.mp3 } { } \l_tmpa_tl
    \iow_term:e {
        \iow_newline:
        ``bird.mp3''~ path:~ \tl_to_str:N \l_tmpa_tl
        \iow_newline:
    }
\ExplSyntaxOff

\begin{document}
    \begin{frame}{Title One}
        Body One
    \end{frame}

    \begin{frame}{Title Two}
        %% Replace this path with the path output to the terminal earlier
        \playaudio{/usr/local/texlive/2024/texmf-dist/source/latex/media9/files/bird.mp3}

        Body Two A
        \begin{itemize}
            \item<2-> Body Two B
            \item<3-> Body Two C
        \end{itemize}
    \end{frame}

    \begin{frame}{Title Three}
        \stopaudio
        Body Three
    \end{frame}

    \begin{frame}{Title Four}
        Body Four
    \end{frame}
\end{document}

마지막으로 웹 브라우저에서 열고 viewer.html"찾아보기"를 클릭한 다음 위에서 컴파일한 PDF 파일을 엽니다. 아무 곳이나 클릭하면 프레젠테이션이 전체 화면으로 전환되고, [ Left Click, , Page ↓, Space, Enter]는 한 슬라이드 앞으로 이동하고, [ , Page ↑, Backspace]는 한 슬라이드 뒤로 이동합니다.

작동 원리

TeX 측에서는 사운드 파일을 PDF "첨부 파일"로 추가한 다음 숨겨진 PDF "주석" 내부에 재생/중지 명령을 배치합니다.

HTML 측에서는 뷰어의 기반으로 사용할 pdf.js를 가져온 다음 각 페이지의 주석을 구문 분석하고 이를 사용하여 첨부된 오디오 파일을 재생/중지할 시기를 결정합니다.

시청자는매우기본이며 그다지 견고하지는 않지만 일반적으로 기본 슬라이드 쇼에는 충분합니다. 새로운 기능을 추가하고 더욱 강력하게 만드는 것은 독자의 과제로 남겨집니다. :)

답변2

이것은 답변이 아니라 문제에 대한 나의 해결책입니다. 나는 본질적으로 설명된 문제에 직면했고 꽤 많은 노력 끝에 현재 PDF 보기 범위 내에 있지 않다는 결론을 내렸습니다.

따라서 저는 비머 기반 PDF 프리젠테이션에서reveal.js와 같은 프레임워크를 사용하는 웹 기반 프리젠테이션으로 전환했습니다. 이는 일반적인 js 기반 라텍스 렌더링 엔진이 라텍스의 하위 집합만 제공하므로 슬라이드 제작 시간이 길어진다는 단점이 있습니다.

새로운 접근 방식의 가장 큰 단점은 노동 집약적이라는 것입니다. 즉, KaTex 또는 유사한 라텍스 기반 웹 렌더러에서 지원하지 않는 특정 라텍스 렌더링이 필요한 경우 먼저 표준 라텍스를 PNG 파일로 렌더링하고 필요한 경우 슬라이드에 이미지로 포함합니다. 가장 큰 이점은 오디오 콘텐츠와 비디오 콘텐츠를 추가하는 것이 쉬워지고 대화형 콘텐츠(자바스크립트 프로그램 형식)를 추가하는 것이 매우 가능하지만 맞춤형 작업이라는 것입니다. 내 작업에는 알고리즘 구현이 포함되는 경우가 많으므로 프레젠테이션 내에서 작업을 원활하게 표시할 수 있는 것이 좋습니다(Alt-Tab을 누르지 않는 등). Reveal.js에는 애니메이션 및 대화형 콘텐츠 위에 마크업 및 스케치를 허용하는 라이브러리도 있습니다. 나는 이 접근법의 주된 단점이 시간이 지나면서 극복되어 모든 세계에서 가장 좋은 것을 제공하는 옵션을 남기기를 바라고 있습니다.

관련 정보