我想在投影機簡報中包含聲音,並且希望該聲音從某張投影片開始並持續多個疊加或多張投影片。我嘗試使用media9
or來實現這一點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 查看的範圍內。
因此,我已從基於 Beamer 的 PDF 簡報切換到使用 Reveal.js 等框架的基於 Web 的簡報。這樣做的缺點是典型的基於 js 的 Latex 渲染引擎僅提供 Latex 的子集,因此幻燈片的製作時間增加了。
新方法的主要缺點是它更勞力密集。也就是說,如果我確實需要 KaTex 或類似的基於乳膠的 Web 渲染器不支援的某些乳膠渲染,我首先將標準乳膠渲染為 PNG 文件,在需要的地方作為圖像嵌入到幻燈片上。最大的收穫是添加音訊內容和視訊內容變得很容易,並且添加互動式內容(以 javascript 程式的形式)是非常可行的但客製化的工作。我的工作通常包括演算法實現,因此很高興能夠在簡報中無縫地展示工作(沒有替代選項卡等)。 Reveal.js 的庫甚至允許在動畫和互動內容之上進行標記和繪製草圖。我希望隨著時間的推移,這種方法的主要缺點能夠被克服,留下一個提供最好的選擇。