หน้านี้มีข้อมูลโค้ดและคำอธิบายของฟีเจอร์ที่พร้อมใช้งานสำหรับ แอป Custom เว็บสโตร์
- องค์ประกอบ
cast-media-player
ที่แสดงถึง UI ของโปรแกรมเล่นในตัวที่มาพร้อมกับเว็บรีซีฟเวอร์ - การจัดรูปแบบที่คล้าย CSS ที่กำหนดเองสำหรับองค์ประกอบ
cast-media-player
เพื่อจัดรูปแบบองค์ประกอบ UI ต่างๆ เช่นbackground-image
,splash-image
และfont-family
- องค์ประกอบสคริปต์สำหรับโหลดเฟรมเวิร์กเว็บรีซีฟเวอร์
- โค้ด JavaScript เพื่อสกัดกั้นข้อความและจัดการเหตุการณ์
- คิวสำหรับการเล่นอัตโนมัติ
- ตัวเลือกในการกำหนดค่าการเล่น
- ตัวเลือกในการตั้งค่าบริบทของตัวรับเว็บ
- ตัวเลือกในการตั้งค่าคำสั่งที่แอป Webตัวรับรองรับ
- การเรียก JavaScript เพื่อเริ่มแอปพลิเคชันเว็บรีซีฟเวอร์
การกำหนดค่าแอปพลิเคชันและตัวเลือก
กำหนดค่าแอปพลิเคชัน
CastReceiverContext
เป็นคลาสชั้นนอกสุดที่นักพัฒนาแอปเห็น มีหน้าที่จัดการการโหลดไลบรารีที่มีอยู่และจัดการการเริ่มต้นใช้งาน SDK ของเว็บรีซีฟเวอร์ SDK มี API ที่อนุญาตให้นักพัฒนาแอปพลิเคชันกำหนดค่า SDK ผ่าน CastReceiverOptions
ระบบจะประเมินการกำหนดค่าเหล่านี้เพียงครั้งเดียวต่อการเปิดแอปพลิเคชันและส่งไปยัง SDK เมื่อตั้งค่าพารามิเตอร์ที่ไม่บังคับในการเรียกใช้ไปยัง start
ตัวอย่างด้านล่างแสดงวิธีลบล้างการทำงานเริ่มต้นเพื่อตรวจหาว่าการเชื่อมต่อของผู้ส่งยังเชื่อมต่ออยู่ เมื่อเว็บรีซีฟเวอร์ไม่สามารถสื่อสารกับผู้ส่งได้เป็นเวลา maxInactivity
วินาที ระบบจะส่งเหตุการณ์ SENDER_DISCONNECTED
การกำหนดค่าด้านล่างจะลบล้างระยะหมดเวลานี้ วิธีนี้มีประโยชน์เวลาแก้ไขข้อบกพร่อง เพราะจะทำให้แอป Web Getr ปิดเซสชันโปรแกรมแก้ไขข้อบกพร่องระยะไกลของ Chrome ไม่ได้เมื่อไม่มีผู้ส่งที่เชื่อมต่อในสถานะ IDLE
const context = cast.framework.CastReceiverContext.getInstance();
const options = new cast.framework.CastReceiverOptions();
options.maxInactivity = 3600; // Development only
context.start(options);
กำหนดค่าโปรแกรมเล่น
เมื่อโหลดเนื้อหา SDK ของตัวรับสัญญาณเว็บเป็นวิธีกำหนดค่าตัวแปรการเล่น เช่น ข้อมูล DRM, ลองกำหนดค่าอีกครั้ง และตัวแฮนเดิลคำขอโดยใช้ cast.framework.PlaybackConfig
ข้อมูลนี้จัดการโดย
PlayerManager
และจะประเมินเมื่อสร้างโปรแกรมเล่น ระบบจะสร้างโปรแกรมเล่นขึ้นทุกครั้งที่โหลดใหม่ไปยัง SDK ของ Webตัวรับสัญญาณ การแก้ไข PlaybackConfig
หลังจากที่สร้างโปรแกรมเล่นแล้วจะได้รับการประเมินในการโหลดเนื้อหาถัดไป SDK มีวิธีแก้ไข PlaybackConfig
ดังต่อไปนี้
CastReceiverOptions.playbackConfig
เพื่อลบล้างตัวเลือกการกำหนดค่าเริ่มต้นเมื่อเริ่มต้นCastReceiverContext
PlayerManager.getPlaybackConfig()
เพื่อรับการกำหนดค่าปัจจุบันPlayerManager.setPlaybackConfig()
เพื่อลบล้างการกำหนดค่าปัจจุบัน การตั้งค่านี้จะมีผลกับการโหลดครั้งต่อๆ ไปทั้งหมดหรือจนกว่าจะมีการลบล้างอีกครั้งPlayerManager.setMediaPlaybackInfoHandler()
เพื่อใช้การกำหนดค่าเพิ่มเติมเฉพาะกับรายการสื่อที่โหลดทับการกำหนดค่าปัจจุบัน ระบบจะเรียกเครื่องจัดการก่อนการสร้าง โปรแกรมเล่น การเปลี่ยนแปลงที่ทำที่นี่ไม่ใช่แบบถาวรและไม่รวมอยู่ในคำค้นหาสำหรับgetPlaybackConfig()
เมื่อโหลดรายการสื่อถัดไปแล้ว ตัวแฮนเดิลนี้จะถูกเรียกอีกครั้ง
ตัวอย่างด้านล่างแสดงวิธีตั้งค่า PlaybackConfig
เมื่อเริ่มต้น CastReceiverContext
การกำหนดค่าจะลบล้างคำขอขาออกสำหรับการรับไฟล์ Manifest เครื่องจัดการระบุว่าคำขอควบคุมการเข้าถึง CORS ควรทำโดยใช้ข้อมูลเข้าสู่ระบบ เช่น คุกกี้หรือส่วนหัวการให้สิทธิ์
const playbackConfig = new cast.framework.PlaybackConfig();
playbackConfig.manifestRequestHandler = requestInfo => {
requestInfo.withCredentials = true;
};
context.start({playbackConfig: playbackConfig});
ตัวอย่างด้านล่างแสดงวิธีลบล้าง PlaybackConfig
โดยใช้ Getter และ Setter ที่ให้ไว้ใน PlayerManager
การตั้งค่านี้จะกำหนดค่าให้โปรแกรมเล่นเล่นเนื้อหาต่อหลังจากที่โหลด 1 ส่วนแล้ว
const playerManager =
cast.framework.CastReceiverContext.getInstance().getPlayerManager();
const playbackConfig = (Object.assign(
new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig()));
playbackConfig.autoResumeNumberOfSegments = 1;
playerManager.setPlaybackConfig(playbackConfig);
ตัวอย่างด้านล่างแสดงวิธีลบล้าง PlaybackConfig
สำหรับคำขอโหลดที่เจาะจงโดยใช้ตัวแฮนเดิลข้อมูลการเล่นสื่อ เครื่องจัดการจะเรียกเมธอด getLicenseUrlForMedia
ของแอปพลิเคชันที่ใช้งานเพื่อรับ licenseUrl
จาก contentId
ของรายการปัจจุบัน
playerManager.setMediaPlaybackInfoHandler((loadRequestData, playbackConfig) => {
const mediaInformation = loadRequestData.media;
playbackConfig.licenseUrl = getLicenseUrlForMedia(mediaInformation.contentId);
return playbackConfig;
});
Listener เหตุการณ์
SDK ตัวรับสัญญาณเว็บช่วยให้แอปตัวรับสัญญาณเว็บของคุณจัดการเหตุการณ์ของโปรแกรมเล่นได้ Listener เหตุการณ์จะใช้พารามิเตอร์ cast.framework.events.EventType
(หรืออาร์เรย์ของพารามิเตอร์เหล่านี้) ซึ่งระบุเหตุการณ์ที่ควรทริกเกอร์ Listener ดูอาร์เรย์ที่กำหนดค่าไว้ล่วงหน้าของ cast.framework.events.EventType
ซึ่งมีประโยชน์ต่อการแก้ไขข้อบกพร่องได้ใน cast.framework.events.category
พารามิเตอร์เหตุการณ์ให้ข้อมูลเพิ่มเติมเกี่ยวกับเหตุการณ์
ตัวอย่างเช่น หากคุณต้องการทราบเวลาที่ระบบเผยแพร่การเปลี่ยนแปลง mediaStatus
คุณสามารถใช้ตรรกะต่อไปนี้เพื่อจัดการกับเหตุการณ์ได้
const playerManager =
cast.framework.CastReceiverContext.getInstance().getPlayerManager();
playerManager.addEventListener(
cast.framework.events.EventType.MEDIA_STATUS, (event) => {
// Write your own event handling code, for example
// using the event.mediaStatus value
});
การสกัดกั้นข้อความ
SDK ตัวรับสัญญาณเว็บช่วยให้แอปตัวรับสัญญาณเว็บของคุณดักจับข้อความและเรียกใช้โค้ดที่กำหนดเองบนข้อความเหล่านั้นได้ ตัวตรวจจับข้อความจะใช้พารามิเตอร์
cast.framework.messages.MessageType
ที่ระบุประเภทของข้อความที่ควรสกัดกั้น
ผู้สกัดกั้นควรแสดงผลคำขอที่แก้ไขหรือคำสัญญาที่แก้ไขด้วยค่าคำขอที่แก้ไข การแสดงผล null
จะป้องกันไม่ให้เรียกใช้ตัวแฮนเดิลข้อความเริ่มต้น ดูรายละเอียดเพิ่มเติมในการโหลดสื่อ
ตัวอย่างเช่น หากต้องการเปลี่ยนข้อมูลคำขอโหลด คุณสามารถใช้ตรรกะต่อไปนี้ในการสกัดกั้นและแก้ไขข้อมูลนั้น
const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD, loadRequestData => {
const error = new cast.framework.messages.ErrorData(
cast.framework.messages.ErrorType.LOAD_FAILED);
if (!loadRequestData.media) {
error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
return error;
}
if (!loadRequestData.media.entity) {
return loadRequestData;
}
return thirdparty.fetchAssetAndAuth(loadRequestData.media.entity,
loadRequestData.credentials)
.then(asset => {
if (!asset) {
throw cast.framework.messages.ErrorReason.INVALID_REQUEST;
}
loadRequestData.media.contentUrl = asset.url;
loadRequestData.media.metadata = asset.metadata;
loadRequestData.media.tracks = asset.tracks;
return loadRequestData;
}).catch(reason => {
error.reason = reason; // cast.framework.messages.ErrorReason
return error;
});
});
context.start();
การจัดการข้อผิดพลาด
เมื่อเกิดข้อผิดพลาดในตัวดักจับข้อความ แอปเว็บรีซีฟเวอร์ควรแสดงผล cast.framework.messages.ErrorType
และ cast.framework.messages.ErrorReason
ที่เหมาะสม
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD, loadRequestData => {
const error = new cast.framework.messages.ErrorData(
cast.framework.messages.ErrorType.LOAD_CANCELLED);
if (!loadRequestData.media) {
error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
return error;
}
...
return fetchAssetAndAuth(loadRequestData.media.entity,
loadRequestData.credentials)
.then(asset => {
...
return loadRequestData;
}).catch(reason => {
error.reason = reason; // cast.framework.messages.ErrorReason
return error;
});
});
การสกัดกั้นข้อความกับ Listener เหตุการณ์
ความแตกต่างที่สําคัญระหว่างการสกัดกั้นข้อความและ Listener เหตุการณ์มีดังนี้
- Listener เหตุการณ์ไม่อนุญาตให้คุณแก้ไขข้อมูลคำขอ
- Listener เหตุการณ์จะใช้เพื่อทริกเกอร์ Analytics หรือฟังก์ชันที่กำหนดเองได้ดีที่สุด
playerManager.addEventListener(cast.framework.events.category.CORE,
event => {
console.log(event);
});
- การสกัดกั้นข้อความช่วยให้คุณฟังข้อความ ดักจับข้อความ และแก้ไขข้อมูลคำขอได้
- การสกัดกั้นข้อความจะใช้ในการจัดการตรรกะที่กำหนดเองเกี่ยวกับข้อมูลคำขอได้ดีที่สุด
กำลังโหลดสื่อ
MediaInformation
มีพร็อพเพอร์ตี้มากมายสำหรับโหลดสื่อในข้อความ cast.framework.messages.MessageType.LOAD
เช่น entity
, contentUrl
และ contentId
entity
เป็นพร็อพเพอร์ตี้ที่แนะนำเพื่อใช้ในการติดตั้งใช้งานทั้งสำหรับแอปผู้ส่งและผู้รับ พร็อพเพอร์ตี้คือ URL ของ Deep Link ที่เป็นได้ทั้งเพลย์ลิสต์หรือเนื้อหาสื่อ คุณควรแยกวิเคราะห์ URL นี้และ ป้อนข้อมูลในช่องอื่นๆ อย่างน้อย 1 ช่องcontentUrl
ตรงกับ URL ที่เล่นได้ซึ่งโปรแกรมเล่นจะใช้เพื่อโหลดเนื้อหา ตัวอย่างเช่น URL นี้อาจชี้ไปยังไฟล์ Manifest ของ DASHcontentId
อาจเป็น URL เนื้อหาที่เล่นได้ (คล้ายกับของพร็อพเพอร์ตี้contentUrl
) หรือตัวระบุที่ไม่ซ้ำกันสำหรับเนื้อหาหรือเพลย์ลิสต์ที่โหลด หากใช้พร็อพเพอร์ตี้นี้เป็นตัวระบุ แอปพลิเคชันควรสร้าง URL ที่เล่นได้ในcontentUrl
ขอแนะนำให้ใช้ entity
เพื่อจัดเก็บรหัสจริงหรือพารามิเตอร์คีย์ และใช้ contentUrl
สำหรับ URL ของสื่อ ตัวอย่างข้อมูลจะแสดงในข้อมูลโค้ดต่อไปนี้ซึ่งมี entity
อยู่ในคำขอ LOAD
และมีการดึงข้อมูล contentUrl
ที่เล่นได้
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD, loadRequestData => {
...
if (!loadRequestData.media.entity) {
// Copy the value from contentId for legacy reasons if needed
loadRequestData.media.entity = loadRequestData.media.contentId;
}
return thirdparty.fetchAssetAndAuth(loadRequestData.media.entity,
loadRequestData.credentials)
.then(asset => {
loadRequestData.media.contentUrl = asset.url;
...
return loadRequestData;
});
});
ความสามารถของอุปกรณ์
เมธอด getDeviceCapabilities
จะให้ข้อมูลอุปกรณ์ในอุปกรณ์แคสต์ที่เชื่อมต่อและอุปกรณ์วิดีโอหรือเสียงที่ต่อเชื่อมอยู่ เมธอด getDeviceCapabilities
จะให้ข้อมูลการรองรับสำหรับ Google Assistant, บลูทูธ รวมถึงอุปกรณ์แสดงผลและอุปกรณ์เสียงที่เชื่อมต่อ
เมธอดนี้จะแสดงออบเจ็กต์ที่คุณค้นหาได้โดยการส่งผ่าน enum ที่ระบุค่าใดค่าหนึ่งเพื่อรับความสามารถของอุปกรณ์สำหรับ Enum ดังกล่าว Enum มีคำจำกัดความใน cast.framework.system.DeviceCapabilities
ตัวอย่างนี้จะตรวจสอบว่าอุปกรณ์เว็บรีซีฟเวอร์สามารถเล่น HDR และDolbyVision (DV) ด้วยคีย์ IS_HDR_SUPPORTED
และ IS_DV_SUPPORTED
ตามลำดับได้หรือไม่
const context = cast.framework.CastReceiverContext.getInstance();
context.addEventListener(cast.framework.system.EventType.READY, () => {
const deviceCapabilities = context.getDeviceCapabilities();
if (deviceCapabilities &&
deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_HDR_SUPPORTED]) {
// Write your own event handling code, for example
// using the deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_HDR_SUPPORTED] value
}
if (deviceCapabilities &&
deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_DV_SUPPORTED]) {
// Write your own event handling code, for example
// using the deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_DV_SUPPORTED] value
}
});
context.start();
การจัดการการโต้ตอบของผู้ใช้
ผู้ใช้สามารถโต้ตอบกับแอปพลิเคชันเว็บรีซีฟเวอร์ของคุณผ่านแอปพลิเคชันของผู้ส่ง (เว็บ, Android และ iOS) คำสั่งเสียงในอุปกรณ์ที่พร้อมใช้งาน Assistant การควบคุมด้วยการสัมผัสบนจออัจฉริยะ และรีโมตคอนโทรลในอุปกรณ์ Android TV Cast SDK มี API ต่างๆ เพื่ออนุญาตให้แอปเว็บรีซีฟเวอร์จัดการการโต้ตอบเหล่านี้ อัปเดต UI ของแอปพลิเคชันผ่านสถานะการดำเนินการของผู้ใช้ และคุณเลือกที่จะส่งการเปลี่ยนแปลงเพื่ออัปเดตบริการแบ็กเอนด์ใดก็ได้
คำสั่งสื่อที่รองรับ
สถานะการควบคุม UI ขับเคลื่อนโดย MediaStatus.supportedMediaCommands
สำหรับตัวควบคุมแบบขยายสำหรับผู้ส่งของ iOS และ Android, แอปตัวรับและรีโมตคอนโทรล
ที่ทำงานในอุปกรณ์แบบสัมผัส และแอปตัวรับบนอุปกรณ์ Android TV เมื่อเปิดใช้ Command
บางบิตในพร็อพเพอร์ตี้ ปุ่มที่เกี่ยวข้องกับการดำเนินการนั้นจะเปิดใช้ หากไม่ได้ตั้งค่า ปุ่มจะถูกปิด ค่าเหล่านี้จะเปลี่ยนแปลงได้ใน Web "รอ" โดยดำเนินการดังนี้
- การใช้
PlayerManager.setSupportedMediaCommands
เพื่อกำหนดCommands
- เพิ่มคำสั่งใหม่โดยใช้
addSupportedMediaCommands
- นำคำสั่งที่มีอยู่ออกโดยใช้
removeSupportedMediaCommands
playerManager.setSupportedMediaCommands(cast.framework.messages.Command.SEEK |
cast.framework.messages.Command.PAUSE);
เมื่อผู้รับเตรียม MediaStatus
ที่อัปเดตแล้ว เซิร์ฟเวอร์จะรวมการเปลี่ยนแปลงในพร็อพเพอร์ตี้ supportedMediaCommands
ด้วย เมื่อมีการประกาศสถานะ แอปของผู้ส่งที่เชื่อมต่อจะอัปเดตปุ่มใน UI ให้สอดคล้องกัน
ดูข้อมูลเพิ่มเติมเกี่ยวกับคำสั่งของสื่อและอุปกรณ์สัมผัสที่รองรับได้จากคำแนะนำ Accessing UI controls
การจัดการสถานะการดำเนินการของผู้ใช้
เมื่อโต้ตอบกับ UI หรือส่งคำสั่งเสียง ผู้ใช้จะควบคุมการเล่นเนื้อหาและพร็อพเพอร์ตี้ที่เกี่ยวข้องกับรายการที่กำลังเล่นได้ SDK จะจัดการกับคำขอที่ควบคุมการเล่นโดยอัตโนมัติ คำขอที่แก้ไขพร็อพเพอร์ตี้สำหรับรายการปัจจุบันที่เล่นอยู่ เช่น คำสั่ง LIKE
จำเป็นต้องให้แอปพลิเคชันฝั่งผู้รับจัดการคำขอเหล่านั้น SDK มีชุด API สำหรับจัดการคำขอประเภทนี้ คุณต้องดำเนินการต่อไปนี้เพื่อรองรับคำขอเหล่านี้
- ตั้งค่า
MediaInformation
userActionStates
ด้วยค่ากำหนดของผู้ใช้เมื่อโหลดรายการสื่อ - สกัดกั้น
USER_ACTION
ข้อความและพิจารณาการดำเนินการที่ขอ - อัปเดต
UserActionState
ของMediaInformation
เพื่ออัปเดต UI
ข้อมูลโค้ดต่อไปนี้สกัดกั้นคำขอ LOAD
และป้อนข้อมูล MediaInformation
ของ LoadRequestData
ในกรณีนี้ ผู้ใช้ชอบเนื้อหาที่โหลด
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD, (loadRequestData) => {
const userActionLike = new cast.framework.messages.UserActionState(
cast.framework.messages.UserAction.LIKE);
loadRequestData.media.userActionStates = [userActionLike];
return loadRequestData;
});
ข้อมูลโค้ดต่อไปนี้จะดักฟังข้อความ USER_ACTION
และจัดการกับการเรียกใช้แบ็กเอนด์ที่มีการเปลี่ยนแปลงที่ขอ จากนั้นระบบจะทำการโทรเพื่ออัปเดต UserActionState
ในเครื่องรับ
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.USER_ACTION,
(userActionRequestData) => {
// Obtain the media information of the current content to associate the action to.
let mediaInfo = playerManager.getMediaInformation();
// If there is no media info return an error and ignore the request.
if (!mediaInfo) {
console.error('Not playing media, user action is not supported');
return new cast.framework.messages.ErrorData(messages.ErrorType.BAD_REQUEST);
}
// Reach out to backend services to store user action modifications. See sample below.
return sendUserAction(userActionRequestData, mediaInfo)
// Upon response from the backend, update the client's UserActionState.
.then(backendResponse => updateUserActionStates(backendResponse))
// If any errors occurred in the backend return them to the cast receiver.
.catch((error) => {
console.error(error);
return error;
});
});
ข้อมูลโค้ดต่อไปนี้จะจำลองการเรียกไปยังบริการแบ็กเอนด์ ฟังก์ชันนี้จะตรวจสอบ UserActionRequestData
เพื่อดูประเภทการเปลี่ยนแปลงที่ผู้ใช้ขอและเรียกเครือข่ายก็ต่อเมื่อแบ็กเอนด์รองรับการดำเนินการดังกล่าวเท่านั้น
function sendUserAction(userActionRequestData, mediaInfo) {
return new Promise((resolve, reject) => {
switch (userActionRequestData.userAction) {
// Handle user action changes supported by the backend.
case cast.framework.messages.UserAction.LIKE:
case cast.framework.messages.UserAction.DISLIKE:
case cast.framework.messages.UserAction.FOLLOW:
case cast.framework.messages.UserAction.UNFOLLOW:
case cast.framework.messages.UserAction.FLAG:
case cast.framework.messages.UserAction.SKIP_AD:
let backendResponse = {userActionRequestData: userActionRequestData, mediaInfo: mediaInfo};
setTimeout(() => {resolve(backendResponse)}, 1000);
break;
// Reject all other user action changes.
default:
reject(
new cast.framework.messages.ErrorData(cast.framework.messages.ErrorType.INVALID_REQUEST));
}
});
}
ข้อมูลโค้ดต่อไปนี้จะใช้ UserActionRequestData
และเพิ่มหรือนำ UserActionState
ออกจาก MediaInformation
การอัปเดต UserActionState
ของ MediaInformation
จะเปลี่ยนสถานะของปุ่มที่เชื่อมโยงกับการดำเนินการที่ขอ การเปลี่ยนแปลงนี้จะแสดงใน UI การควบคุมจออัจฉริยะ, แอปรีโมตคอนโทรล และ UI ของ Android TV และจะยังเผยแพร่ผ่านข้อความขาออก MediaStatus
เพื่ออัปเดต UI ของตัวควบคุมแบบขยายสำหรับผู้ส่ง iOS และ Android
function updateUserActionStates(backendResponse) {
// Unwrap the backend response.
let mediaInfo = backendResponse.mediaInfo;
let userActionRequestData = backendResponse.userActionRequestData;
// If the current item playing has changed, don't update the UserActionState for the current item.
if (playerManager.getMediaInformation().entity !== mediaInfo.entity) {
return;
}
// Check for existing userActionStates in the MediaInformation.
// If none, initialize a new array to populate states with.
let userActionStates = mediaInfo.userActionStates || [];
// Locate the index of the UserActionState that will be updated in the userActionStates array.
let index = userActionStates.findIndex((currUserActionState) => {
return currUserActionState.userAction == userActionRequestData.userAction;
});
if (userActionRequestData.clear) {
// Remove the user action state from the array if cleared.
if (index >= 0) {
userActionStates.splice(index, 1);
}
else {
console.warn("Could not find UserActionState to remove in MediaInformation");
}
} else {
// Add the UserActionState to the array if enabled.
userActionStates.push(
new cast.framework.messages.UserActionState(userActionRequestData.userAction));
}
// Update the UserActionState array and set the new MediaInformation
mediaInfo.userActionStates = userActionStates;
playerManager.setMediaInformation(mediaInfo, true);
return;
}
คำสั่งเสียง
ปัจจุบัน SDK ตัวรับสัญญาณเว็บสําหรับอุปกรณ์ที่พร้อมใช้งาน Assistant รองรับคําสั่งสื่อต่อไปนี้ การใช้งานเริ่มต้นของคำสั่งเหล่านี้จะอยู่ใน cast.framework.PlayerManager
คำสั่ง | คำอธิบาย |
---|---|
เล่น | เล่นหรือกลับมาเล่นต่อจากสถานะหยุดชั่วคราว |
หยุดชั่วคราว | หยุดเนื้อหาที่เล่นอยู่ในปัจจุบันชั่วคราว |
ก่อนหน้า | ข้ามไปที่รายการสื่อก่อนหน้าในคิวสื่อของคุณ |
ถัดไป | ข้ามไปยังรายการสื่อถัดไปในคิวสื่อของคุณ |
หยุด | หยุดสื่อที่เล่นอยู่ |
ไม่ทำซ้ำ | ปิดใช้การทำซ้ำรายการสื่อในคิวเมื่อเล่นรายการสุดท้ายในคิวเสร็จแล้ว |
เล่นซิงเกิลซ้ำ | เล่นสื่อที่กำลังเล่นอยู่ซ้ำไปเรื่อยๆ |
ทำซ้ำทั้งหมด | ทำซ้ำรายการทั้งหมดในคิวเมื่อมีการเล่นรายการสุดท้ายในคิว |
เล่นซ้ำทั้งหมดและสุ่มเพลง | เมื่อเล่นรายการสุดท้ายในคิวเสร็จแล้ว ให้สับเปลี่ยนคิวและทำซ้ำรายการทั้งหมดในคิว |
สุ่มเพลง | สุ่มรายการสื่อในคิวสื่อ |
เปิด / ปิดคำบรรยาย | เปิด / ปิดใช้คำบรรยายแทนเสียงสำหรับสื่อ การเปิด / ปิดใช้พร้อมใช้งานตามภาษาด้วย |
ค้นหาเวลาสัมบูรณ์ | ข้ามไปยังเวลาสัมบูรณ์ที่ระบุ |
ค้นหาเวลาสัมพัทธ์กับเวลาปัจจุบัน | ข้ามไปข้างหน้าหรือย้อนกลับตามระยะเวลาที่ระบุซึ่งสัมพันธ์กับเวลาการเล่นปัจจุบัน |
เล่นอีกครั้ง | รีสตาร์ทสื่อที่กำลังเล่นอยู่หรือเล่นรายการสื่อที่เล่นล่าสุดหากไม่มีสิ่งใดกำลังเล่นอยู่ |
กำหนดอัตราการเล่น | เปลี่ยนแปลงอัตราการเล่นสื่อ ซึ่งควรได้รับการจัดการโดยค่าเริ่มต้น คุณใช้ตัวตรวจจับข้อความ SET_PLAYBACK_RATE เพื่อลบล้างคำขออัตราที่เข้ามาใหม่ได้ |
คำสั่งสื่อที่รองรับด้วยเสียง
หากต้องการป้องกันไม่ให้คำสั่งเสียงทริกเกอร์คำสั่งสื่อในอุปกรณ์ที่พร้อมใช้งาน Assistant คุณต้องตั้งค่าคำสั่งสื่อที่รองรับซึ่งวางแผนจะรองรับก่อน จากนั้นคุณต้องบังคับใช้คำสั่งเหล่านั้นโดยการเปิดใช้พร็อพเพอร์ตี้ CastReceiverOptions.enforceSupportedCommands
UI ของผู้ส่ง Cast SDK และอุปกรณ์ที่เปิดใช้ระบบสัมผัสจะเปลี่ยนไปตามการกำหนดค่าเหล่านี้ หากไม่ได้เปิดใช้แฟล็ก คำสั่งเสียงที่เข้ามาก็จะทำงาน
ตัวอย่างเช่น หากอนุญาต PAUSE
จากแอปพลิเคชันของผู้ส่งและอุปกรณ์ที่เปิดใช้ระบบสัมผัส คุณต้องกำหนดค่าผู้รับให้ตรงกับการตั้งค่าเหล่านั้นด้วย เมื่อกำหนดค่าแล้ว คำสั่งเสียงที่เข้ามาใหม่จะหายไปหากไม่รวมอยู่ในรายการคำสั่งที่รองรับ
ในตัวอย่างด้านล่าง เราระบุ CastReceiverOptions
เมื่อเริ่มต้น CastReceiverContext
เราได้เพิ่มการรองรับคำสั่ง PAUSE
และบังคับให้โปรแกรมเล่นรองรับเฉพาะคำสั่งดังกล่าวเท่านั้น ในตอนนี้หากคำสั่งเสียงขอการดำเนินการอื่น เช่น SEEK
จะถูกปฏิเสธ ผู้ใช้จะได้รับแจ้งว่ายังไม่รองรับคำสั่งนี้
const context = cast.framework.CastReceiverContext.getInstance();
context.start({
enforceSupportedCommands: true,
supportedCommands: cast.framework.messages.Command.PAUSE
});
โดยคุณสามารถใช้ตรรกะแยกกันสำหรับแต่ละคำสั่งที่ต้องการจำกัดได้ นำแฟล็ก enforceSupportedCommands
ออก และสำหรับแต่ละคำสั่งที่ต้องการจำกัด คุณสามารถสกัดกั้นข้อความขาเข้าได้ ซึ่งตรงนี้เราสกัดกั้นคำขอที่ได้รับจาก SDK เพื่อไม่ให้คำสั่ง SEEK
ที่ออกไปยังอุปกรณ์ที่พร้อมใช้งาน Assistant ทริกเกอร์การค้นหาในแอปพลิเคชันเว็บรีซีฟเวอร์ของคุณ
สำหรับคำสั่งสื่อที่แอปพลิเคชันของคุณไม่รองรับ ให้แสดงผลเหตุผลข้อผิดพลาดที่เหมาะสม เช่น NOT_SUPPORTED
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.SEEK,
seekData => {
// Block seeking if the SEEK supported media command is disabled
if (!(playerManager.getSupportedMediaCommands() & cast.framework.messages.Command.SEEK)) {
let e = new cast.framework.messages.ErrorData(cast.framework.messages.ErrorType
.INVALID_REQUEST);
e.reason = cast.framework.messages.ErrorReason.NOT_SUPPORTED;
return e;
}
return seekData;
});
เล่นอยู่เบื้องหลังจากกิจกรรมเสียง
หากแพลตฟอร์ม Cast ทำให้เสียงของแอปพลิเคชันอยู่เบื้องหลังเนื่องจากกิจกรรมของ Assistant เช่น การฟังคำพูดของผู้ใช้หรือพูดโต้ตอบ ระบบจะส่งข้อความ FocusState
ของ NOT_IN_FOCUS
ไปยังแอปพลิเคชันเว็บรีซีฟเวอร์เมื่อกิจกรรมเริ่มขึ้น ระบบจะส่งข้อความอื่นกับ IN_FOCUS
เมื่อกิจกรรมสิ้นสุดลง
คุณอาจต้องการหยุดสื่อชั่วคราวเมื่อ FocusState
NOT_IN_FOCUS
โดยการสกัดกั้นข้อความประเภท FOCUS_STATE
ทั้งนี้ขึ้นอยู่กับแอปพลิเคชันและสื่อที่เล่นอยู่
เช่น การหยุดเล่นหนังสือเสียงชั่วคราวเป็นประสบการณ์ที่ดีของผู้ใช้หาก Assistant ตอบสนองต่อคำค้นหาของผู้ใช้
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.FOCUS_STATE,
focusStateRequestData => {
// Pause content when the app is out of focus. Resume when focus is restored.
if (focusStateRequestData.state == cast.framework.messages.FocusState.NOT_IN_FOCUS) {
playerManager.pause();
} else {
playerManager.play();
}
return focusStateRequestData;
});
ภาษาของคำบรรยายด้วยเสียง
เมื่อผู้ใช้ไม่ได้ระบุภาษาของคำบรรยายอย่างชัดแจ้ง ภาษาที่ใช้สำหรับคำบรรยายจะเป็นภาษาเดียวกับที่มีการใช้คำสั่ง
ในสถานการณ์เช่นนี้ พารามิเตอร์ isSuggestedLanguage
ของข้อความขาเข้าจะระบุว่าผู้ใช้แนะนําหรือขอภาษาที่เกี่ยวข้องอย่างชัดเจนหรือไม่
เช่น มีการตั้งค่า isSuggestedLanguage
เป็น true
สำหรับคำสั่ง "Ok Google เปิดคำบรรยาย" เนื่องจากภาษาดังกล่าวอนุมานจากภาษาที่พูดในคำสั่ง หากมีการขอภาษาอย่างชัดเจน เช่น ใน "Ok Google เปิดคำบรรยายภาษาอังกฤษ" isSuggestedLanguage
จะตั้งค่าเป็น false
การแคสต์ข้อมูลเมตาและการแคสต์เสียง
แม้ว่าเว็บรีซีฟเวอร์จะจัดการคำสั่งเสียงโดยค่าเริ่มต้น แต่คุณก็ควรตรวจสอบว่าข้อมูลเมตาสำหรับเนื้อหาครบถ้วนและถูกต้อง วิธีนี้ช่วยให้มั่นใจได้ว่า Assistant จัดการคำสั่งเสียงอย่างถูกต้องและข้อมูลเมตาจะแสดงอย่างถูกต้องในอินเทอร์เฟซประเภทใหม่ๆ เช่น แอป Google Home และ Smart Display เช่น Google Home Hub
การโอนสตรีม
การรักษาสถานะเซสชันเป็นพื้นฐานของการโอนสตรีม ซึ่งผู้ใช้ย้ายสตรีมเสียงและวิดีโอที่มีอยู่ในอุปกรณ์ต่างๆ ได้โดยใช้คำสั่งเสียง, แอป Google Home หรือ Smart Display สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อในอุปกรณ์อีกเครื่องหนึ่ง (ปลายทาง) อุปกรณ์แคสต์ที่มีเฟิร์มแวร์เวอร์ชันล่าสุดสามารถใช้เป็นแหล่งที่มาหรือปลายทางในการโอนสตรีมได้
โฟลว์เหตุการณ์สำหรับการโอนสตรีมคือ
- ในอุปกรณ์ต้นทาง ให้ทำดังนี้
- สื่อหยุดเล่น
- แอปพลิเคชันเว็บรีซีฟเวอร์จะได้รับคำสั่งให้บันทึกสถานะสื่อปัจจุบัน
- แอปพลิเคชันเว็บรีซีฟเวอร์หยุดทำงานแล้ว
- ในอุปกรณ์ปลายทาง ให้ทำดังนี้
- โหลดแอปพลิเคชันเว็บรีซีฟเวอร์แล้ว
- แอปพลิเคชันเว็บรีซีฟเวอร์จะได้รับคำสั่งเพื่อกู้คืนสถานะสื่อที่บันทึกไว้
- สื่อกลับมาเล่นต่อ
องค์ประกอบของสถานะสื่อมีดังนี้
- ตำแหน่งหรือการประทับเวลาที่เฉพาะเจาะจงของเพลง วิดีโอ หรือรายการสื่อ
- วิดีโอจะแสดงในคิวที่กว้างกว่า (เช่น เพลย์ลิสต์หรือวิทยุของศิลปิน)
- ผู้ใช้ที่ตรวจสอบสิทธิ์แล้ว
- สถานะการเล่น (เช่น เล่นหรือหยุดชั่วคราว)
กำลังเปิดใช้การโอนสตรีม
วิธีใช้การโอนสตรีมสำหรับเว็บรีซีฟเวอร์
- อัปเดต
supportedMediaCommands
ด้วยคำสั่งSTREAM_TRANSFER
ดังนี้playerManager.addSupportedMediaCommands( cast.framework.messages.Command.STREAM_TRANSFER, true);
- (ไม่บังคับ) ลบล้างตัวตรวจจับข้อความ
SESSION_STATE
และRESUME_SESSION
ตามที่อธิบายไว้ในสถานะเซสชันการเก็บรักษา โดยจะลบล้างเมื่อจำเป็นต้องจัดเก็บข้อมูลที่กำหนดเอง เป็นส่วนหนึ่งของสแนปชอตเซสชันเท่านั้น มิเช่นนั้น การใช้งานเริ่มต้นสำหรับการเก็บรักษาสถานะเซสชันจะรองรับการโอนสตรีม
กำลังเก็บรักษาสถานะเซสชัน
SDK ของตัวรับสัญญาณเว็บมีการใช้งานเริ่มต้นสำหรับแอปเว็บรีซีฟเวอร์ เพื่อคงสถานะเซสชันเอาไว้ด้วยการบันทึกสแนปชอตของสถานะสื่อปัจจุบัน แปลงสถานะเป็นคำขอโหลด และทำให้เซสชันกลับมาทำงานอีกครั้งด้วยคำขอโหลด
คำขอโหลดที่สร้างโดยเว็บรีซีฟเวอร์สามารถลบล้างได้ในตัวตรวจจับข้อความ SESSION_STATE
หากจำเป็น หากคุณต้องการเพิ่มข้อมูลที่กำหนดเองลงในคำขอโหลด เราขอแนะนำให้ใส่ข้อมูลเหล่านั้นไว้ใน loadRequestData.customData
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.SESSION_STATE,
function (sessionState) {
// Override sessionState.loadRequestData if needed.
const newCredentials = updateCredentials_(sessionState.loadRequestData.credentials);
sessionState.loadRequestData.credentials = newCredentials;
// Add custom data if needed.
sessionState.loadRequestData.customData = {
'membership': 'PREMIUM'
};
return sessionState;
});
คุณดึงข้อมูลที่กําหนดเองได้จาก loadRequestData.customData
ในตัวตรวจจับข้อความ RESUME_SESSION
let cred_ = null;
let membership_ = null;
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.RESUME_SESSION,
function (resumeSessionRequest) {
let sessionState = resumeSessionRequest.sessionState;
// Modify sessionState.loadRequestData if needed.
cred_ = sessionState.loadRequestData.credentials;
// Retrieve custom data.
membership_ = sessionState.loadRequestData.customData.membership;
return resumeSessionRequest;
});
การโหลดเนื้อหาล่วงหน้า
เว็บรีซีฟเวอร์รองรับการโหลดรายการสื่อล่วงหน้าหลังจากรายการเล่นปัจจุบันในคิว
การดำเนินการโหลดล่วงหน้าจะดาวน์โหลดส่วนต่างๆ ของรายการที่กำลังจะเกิดขึ้นไว้ล่วงหน้า ข้อกำหนดจะดำเนินการกับค่า preloadTime ในออบเจ็กต์ QueueItem (ค่าเริ่มต้นคือ 20 วินาทีหากไม่ได้ระบุ) เวลาจะแสดงเป็นวินาที ซึ่งสัมพันธ์กับจุดสิ้นสุดของรายการที่กำลังเล่นอยู่ ใช้ได้เฉพาะค่าที่เป็นบวก ตัวอย่างเช่น หากค่าคือ 10 วินาที รายการนี้จะโหลดล่วงหน้า 10 วินาทีก่อนที่รายการก่อนหน้าจะเสร็จสิ้น หากเวลาที่ใช้ในการโหลดล่วงหน้าสูงกว่าเวลาที่เหลือใน currentItem การโหลดล่วงหน้าจะเกิดขึ้นโดยเร็วที่สุดเท่าที่ทำได้ ดังนั้น ถ้ามีการระบุค่าการโหลดล่วงหน้าที่สูงมากในQueueItem ค่านั้นอาจส่งผลเมื่อใดก็ตามที่เราเล่นรายการปัจจุบันที่เราโหลดรายการถัดไปไว้ล่วงหน้า อย่างไรก็ตาม เราจะคงการตั้งค่าและตัวเลือกนี้ไว้ให้กับนักพัฒนาซอฟต์แวร์ เนื่องจากค่านี้อาจส่งผลต่อแบนด์วิดท์และประสิทธิภาพการสตรีมของรายการที่เล่นปัจจุบัน
โดยค่าเริ่มต้น การโหลดล่วงหน้าจะใช้ได้กับเนื้อหา HLS, DASH และ Smooth สตรีมมิง
ระบบจะไม่โหลดไฟล์วิดีโอและเสียง MP4 ปกติ เช่น MP3 ไว้ล่วงหน้า เนื่องจากอุปกรณ์แคสต์รองรับองค์ประกอบสื่อเพียงองค์ประกอบเดียว และจะใช้โหลดล่วงหน้าในขณะที่รายการเนื้อหาที่มีอยู่ยังคงเล่นอยู่ไม่ได้
ข้อความที่กำหนดเอง
การแลกเปลี่ยนข้อความเป็นวิธีการโต้ตอบที่สำคัญสำหรับแอปพลิเคชันเว็บรีซีฟเวอร์
ผู้ส่งออกข้อความไปยังเว็บรีซีฟเวอร์โดยใช้ API ของผู้ส่งสำหรับแพลตฟอร์มที่ผู้ส่งทำงานอยู่ (Android, iOS, เว็บ) ออบเจ็กต์เหตุการณ์ (ซึ่งก็คือการแสดงข้อความ) ที่ส่งไปยัง Listener เหตุการณ์มีองค์ประกอบข้อมูล (event.data
) ซึ่งข้อมูลจะนำพร็อพเพอร์ตี้ของประเภทเหตุการณ์ที่เฉพาะเจาะจงไปใช้
แอปพลิเคชันเว็บรีซีฟเวอร์อาจเลือกฟังข้อความในเนมสเปซที่ระบุ ด้วยการดำเนินการดังกล่าว แอปพลิเคชันเว็บรีซีฟเวอร์จะรองรับโปรโตคอลเนมสเปซดังกล่าว จากนั้นก็จะขึ้นอยู่กับผู้ส่งที่เชื่อมโยงที่ต้องการสื่อสารบนเนมสเปซนั้นเพื่อใช้โปรโตคอลที่เหมาะสม
เนมสเปซทั้งหมดจะกำหนดโดยสตริงและต้องขึ้นต้นด้วย "urn:x-cast:
" ตามด้วยสตริง ตัวอย่างเช่น
"urn:x-cast:com.example.cast.mynamespace
"
ต่อไปนี้เป็นข้อมูลโค้ดเพื่อให้เว็บรีซีฟเวอร์ฟังข้อความที่กำหนดเองจากผู้ส่งที่เชื่อมต่อ
const context = cast.framework.CastReceiverContext.getInstance();
const CUSTOM_CHANNEL = 'urn:x-cast:com.example.cast.mynamespace';
context.addCustomMessageListener(CUSTOM_CHANNEL, function(customEvent) {
// handle customEvent.
});
context.start();
ในทำนองเดียวกัน แอปพลิเคชันเว็บรีซีฟเวอร์สามารถแจ้งให้ผู้ส่งทราบเกี่ยวกับสถานะของเว็บรีซีฟเวอร์โดยส่งข้อความถึงผู้ส่งที่เชื่อมต่อ แอปพลิเคชันเว็บรีซีฟเวอร์สามารถส่งข้อความโดยใช้ sendCustomMessage(namespace, senderId, message)
บน CastReceiverContext
ได้
เว็บรีซีฟเวอร์สามารถส่งข้อความไปยังผู้ส่งแต่ละรายได้ ไม่ว่าจะตอบกลับข้อความที่ได้รับ หรือเกิดจากการเปลี่ยนแปลงสถานะของแอปพลิเคชัน นอกเหนือจากการรับส่งข้อความแบบจุดต่อจุด (จำกัดขนาด 64 KB) เว็บรีซีฟเวอร์ยังอาจประกาศข้อความไปยังผู้ส่งที่เชื่อมต่อทุกคนด้วย
แคสต์สำหรับอุปกรณ์เสียง
ดูคู่มือ Google Cast สำหรับอุปกรณ์เสียงสำหรับการรองรับการเล่นเฉพาะเสียง
Android TV
ส่วนนี้จะพูดถึงวิธีที่ Google Web Detect ใช้อินพุตของคุณเป็นการเล่น รวมถึงความเข้ากันได้ของ Android TV
การผสานรวมแอปพลิเคชันของคุณกับรีโมตคอนโทรล
เว็บรีซีฟเวอร์ Google ที่ทำงานบนอุปกรณ์ Android TV จะแปลอินพุตจากอินพุตการควบคุมของอุปกรณ์ (กล่าวคือ รีโมตคอนโทรลแบบใช้มือถือ) เป็นข้อความการเล่นสื่อที่กำหนดไว้สำหรับเนมสเปซของ urn:x-cast:com.google.cast.media
ตามที่อธิบายไว้ในข้อความการเล่นสื่อ แอปพลิเคชันของคุณต้องรองรับข้อความเหล่านี้เพื่อควบคุมการเล่นสื่อของแอปพลิเคชัน เพื่ออนุญาตให้ควบคุมการเล่นจากอินพุตควบคุมของ Android TV ได้
หลักเกณฑ์สำหรับความเข้ากันได้ของ Android TV
ต่อไปนี้คือคำแนะนำและข้อผิดพลาดทั่วไปที่ควรหลีกเลี่ยงเพื่อให้แน่ใจว่าแอปพลิเคชันของคุณใช้งานร่วมกับ Android TV ได้
- โปรดทราบว่าสตริง User Agent มีทั้ง "Android" และ "CrKey" บางเว็บไซต์อาจเปลี่ยนเส้นทางไปยังเว็บไซต์สำหรับอุปกรณ์เคลื่อนที่เท่านั้น เนื่องจากตรวจพบป้ายกำกับ "Android" อย่าคิดไปเองว่า "Android" ในสตริง User Agent บ่งบอกถึงผู้ใช้อุปกรณ์เคลื่อนที่เสมอ
- สแต็กสื่อของ Android อาจใช้ GZIP แบบโปร่งใสในการดึงข้อมูล ตรวจสอบว่าข้อมูลสื่อตอบสนองต่อ
Accept-Encoding: gzip
ได้ - เหตุการณ์สื่อ HTML5 ของ Android TV อาจแสดงขึ้นในช่วงเวลาที่ต่างจาก Chromecast ซึ่งอาจแสดงให้เห็นปัญหาที่ซ่อนอยู่ใน Chromecast
- เมื่ออัปเดตสื่อ ให้ใช้เหตุการณ์ที่เกี่ยวข้องกับสื่อที่ทริกเกอร์โดยองค์ประกอบ
<audio>/<video>
เช่นtimeupdate
,pause
และwaiting
หลีกเลี่ยงการใช้เหตุการณ์ที่เกี่ยวข้องกับเครือข่าย เช่นprogress
,suspend
และstalled
เนื่องจากเหตุการณ์เหล่านี้มักจะขึ้นอยู่กับแพลตฟอร์ม ดูข้อมูลเพิ่มเติมเกี่ยวกับการจัดการเหตุการณ์สื่อในตัวรับได้ที่เหตุการณ์สื่อ - เมื่อกำหนดค่าใบรับรอง HTTPS ของเว็บไซต์ผู้รับ อย่าลืมใส่ใบรับรอง CA กลาง โปรดดูหน้าทดสอบ Qualsys SSL เพื่อยืนยันว่าเส้นทางการรับรองที่เชื่อถือได้สำหรับเว็บไซต์ของคุณมีใบรับรอง CA ที่มีป้ายกำกับว่า “การดาวน์โหลดเพิ่มเติม” ใบรับรองนั้นอาจไม่โหลดในแพลตฟอร์มที่ใช้ Android
- แม้ว่า Chromecast จะแสดงหน้าเครื่องรับบนระนาบกราฟิก 720p แพลตฟอร์มแคสต์อื่นๆ ซึ่งรวมถึง Android TV อาจแสดงหน้าที่มีความละเอียดสูงสุด 1080p ตรวจสอบว่าหน้ารีซีฟเวอร์มีความละเอียดต่างๆ อย่างสง่างาม