在多張幻燈片的投影機簡報中包含聲音

在多張幻燈片的投影機簡報中包含聲音

我想在投影機簡報中包含聲音,並且希望該聲音從某張投影片開始並持續多個疊加或多張投影片。我嘗試使用media9or來實現這一點multimedia(根據文檔,這兩者都應該有效)。但在這兩種情況下(參見下面的 MWE),聲音會在應該開始時開始,但在顯示第 2 項時停止。如何讓聲音持續不只一張投影片(對於其中一個包media9multimedia,我不關心哪一個)?

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 上使用 Adob​​e 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 查看的範圍內。

因此,我已從基於 Beamer 的 PDF 簡報切換到使用 Reveal.js 等框架的基於 Web 的簡報。這樣做的缺點是典型的基於 js 的 Latex 渲染引擎僅提供 Latex 的子集,因此幻燈片的製作時間增加了。

新方法的主要缺點是它更勞力密集。也就是說,如果我確實需要 KaTex 或類似的基於乳膠的 Web 渲染器不支援的某些乳膠渲染,我首先將標準乳膠渲染為 PNG 文件,在需要的地方作為圖像嵌入到幻燈片上。最大的收穫是添加音訊內容和視訊內容變得很容易,並且添加互動式內容(以 javascript 程式的形式)是非常可行的但客製化的工作。我的工作通常包括演算法實現,因此很高興能夠在簡報中無縫地展示工作(沒有替代選項卡等)。 Reveal.js 的庫甚至允許在動畫和互動內容之上進行標記和繪製草圖。我希望隨著時間的推移,這種方法的主要缺點能夠被克服,留下一個提供最好的選擇。

相關內容