diff --git a/css/main.css b/css/main.css index c415a4f..c5e4d6f 100644 --- a/css/main.css +++ b/css/main.css @@ -75,6 +75,14 @@ dl.row dd { animation: glow 1.5s ease-in-out infinite !important; } +.accordion-item:has(.accordion-collapse.show) i.fas.test-icon-lights { + animation: glow 1.2s ease-in-out infinite !important; +} + +.accordion-item:has(.accordion-collapse.show) i.fas.test-icon-headphone { + animation: pulse 1s ease-in-out infinite !important; +} + /* Skip button hover behavior */ .skip-btn { opacity: 0; diff --git a/js/controller-manager.js b/js/controller-manager.js index 810f684..2082601 100644 --- a/js/controller-manager.js +++ b/js/controller-manager.js @@ -382,14 +382,15 @@ class ControllerManager { * Test speaker tone (DS5 only) * @param {number} duration - Duration in milliseconds (optional) * @param {Function} doneCb - Callback function called when tone ends (optional) + * @param {string} output - Audio output destination: "speaker" (default) or "headphones" (optional) */ - async setSpeakerTone(duration = 1000, doneCb = ({success}) => {}) { + async setSpeakerTone(duration = 1000, doneCb = ({success}) => {}, output = "speaker") { try { if (!this.currentController.setSpeakerTone) { throw new Error(this.l("Speaker tone not supported on this controller")); } - await this.currentController.setSpeakerTone(); + await this.currentController.setSpeakerTone(output); // If duration is specified, automatically reset speaker after the duration if (duration > 0) { diff --git a/js/controllers/ds5-controller.js b/js/controllers/ds5-controller.js index eb8b1c8..1ff2bfd 100644 --- a/js/controllers/ds5-controller.js +++ b/js/controllers/ds5-controller.js @@ -685,25 +685,33 @@ class DS5Controller extends BaseController { /** * Test speaker tone by controlling speaker volume and audio settings - * This creates a brief audio feedback through the controller's speaker + * This creates a brief audio feedback through the controller's speaker or headphones + * @param {string} output - Audio output destination: "speaker" (default) or "headphones" */ - async setSpeakerTone() { + async setSpeakerTone(output = "speaker") { try { const { validFlag0 } = this.currentOutputState; const outputStruct = new DS5OutputStruct({ ...this.currentOutputState, speakerVolume: 85, - validFlag0: validFlag0 | DS5_VALID_FLAG0.SPEAKER_VOLUME | DS5_VALID_FLAG0.AUDIO_CONTROL, + headphoneVolume: 55, + validFlag0: validFlag0 | DS5_VALID_FLAG0.HEADPHONE_VOLUME | DS5_VALID_FLAG0.SPEAKER_VOLUME | DS5_VALID_FLAG0.AUDIO_CONTROL, }); - await this.sendOutputReport(outputStruct.pack(), 'play speaker tone'); - outputStruct.validFlag0 &= ~(DS5_VALID_FLAG0.SPEAKER_VOLUME | DS5_VALID_FLAG0.AUDIO_CONTROL); + await this.sendOutputReport(outputStruct.pack(), output === "headphones" ? 'play headphone tone' : 'play speaker tone'); + outputStruct.validFlag0 &= ~(DS5_VALID_FLAG0.HEADPHONE_VOLUME | DS5_VALID_FLAG0.SPEAKER_VOLUME | DS5_VALID_FLAG0.AUDIO_CONTROL); - // Send feature reports to enable speaker audio - // Audio configuration command - await this.sendFeatureReport(128, [6, 4, 0, 0, 8]); - - // Enable speaker tone - await this.sendFeatureReport(128, [6, 2, 1, 1, 0]); + // Send feature reports to enable audio + if (output === "headphones") { + // Audio configuration command for headphones + await this.sendFeatureReport(128, [6, 4, 0, 0, 0, 0, 4, 0, 6]); + // Enable headphone tone + await this.sendFeatureReport(128, [6, 2, 1, 1, 0]); + } else { + // Audio configuration command for speakers + await this.sendFeatureReport(128, [6, 4, 0, 0, 8]); + // Enable speaker tone + await this.sendFeatureReport(128, [6, 2, 1, 1, 0]); + } // Update current state to reflect the changes this.updateCurrentOutputState(outputStruct); diff --git a/js/modals/quick-test-modal.js b/js/modals/quick-test-modal.js index 5f9d963..4f636f3 100644 --- a/js/modals/quick-test-modal.js +++ b/js/modals/quick-test-modal.js @@ -1,16 +1,6 @@ 'use strict'; -const ACCORDION_ELEMENTS = [ - 'usb-test-collapse', - 'buttons-test-collapse', - 'haptic-test-collapse', - 'adaptive-test-collapse', - 'lights-test-collapse', - 'speaker-test-collapse', - 'microphone-test-collapse' -]; - -const TEST_SEQUENCE = ['usb', 'buttons', 'haptic', 'adaptive', 'lights', 'speaker', 'microphone']; +const TEST_SEQUENCE = ['usb', 'buttons', 'haptic', 'adaptive', 'lights', 'speaker', 'headphone', 'microphone']; const TEST_NAMES = { 'usb': 'USB Connector', 'buttons': 'Buttons', @@ -18,7 +8,8 @@ const TEST_NAMES = { 'adaptive': 'Adaptive Trigger', 'lights': 'Lights', 'speaker': 'Speaker', - 'microphone': 'Microphone' + 'headphone': 'Headphone Jack', + 'microphone': 'Microphone', }; const BUTTONS = ['triangle', 'cross', 'circle', 'square', 'l1', 'r1', 'l2', 'r2', 'l3', 'r3', 'up', 'down', 'left', 'right', 'create', 'touchpad', 'options', 'ps', 'mute']; @@ -76,6 +67,7 @@ export class QuickTestModal { lights: null, speaker: null, microphone: null, + headphone: null, microphoneStream: null, microphoneContext: null, microphoneMonitoring: false, @@ -158,7 +150,8 @@ export class QuickTestModal { 'adaptive': 'fas fa-hand-pointer', 'lights': 'fas fa-lightbulb', 'speaker': 'fas fa-volume-up', - 'microphone': 'fas fa-microphone' + 'microphone': 'fas fa-microphone', + 'headphone': 'fas fa-headphones' }; const testContent = this._getTestContent(testType); @@ -308,6 +301,26 @@ export class QuickTestModal { `; + case 'headphone': + return ` +

This test checks the headphone jack functionality.

+

Instructions:

+
    +
  1. Plug in headphones to the 3.5mm jack
  2. +
  3. Click "Test Speaker" to listen for the tone through the headphones
  4. +
+
+ + + +
+ `; default: return ''; } @@ -558,6 +571,9 @@ export class QuickTestModal { case 'microphone': this._startMicrophoneTest(); break; + case 'headphone': + // Headphone test is manual - no auto-start needed + break; } }, 100); } @@ -586,6 +602,9 @@ export class QuickTestModal { case 'microphone': this._stopMicrophoneTest(); break; + case 'headphone': + // Headphone test is manual - no stop needed + break; } // Update instructions when a test is collapsed @@ -913,6 +932,29 @@ export class QuickTestModal { $levelContainer.hide(); } + /** + * Test headphone audio output by playing through controller headphones + * This specifically routes audio to headphones instead of the built-in speaker + */ + async testHeadphoneAudio() { + this._startIconAnimation('headphone'); + + try { + // Play a test tone through the controller's headphone output + // The third parameter specifies "headphones" as the output destination + await this.controller.setSpeakerTone(500, ({success}) => {}, "headphones"); + + // Stop the animation after the tone completes + setTimeout(() => { + this._stopIconAnimation('headphone'); + }, 700); // Slightly longer than tone duration + + } catch (error) { + console.error('Error testing headphone audio:', error); + this._stopIconAnimation('headphone'); + } + } + /** * Mark test result and update UI */ @@ -1458,9 +1500,16 @@ function addTestBack(testType) { } } +function testHeadphoneAudio() { + if (currentQuickTestInstance) { + currentQuickTestInstance.testHeadphoneAudio(); + } +} + // Legacy compatibility - expose functions to window for HTML onclick handlers window.markTestResult = markTestResult; window.resetAllTests = resetAllTests; window.resetButtonsTest = resetButtonsTest; window.skipTest = skipTest; -window.addTestBack = addTestBack; \ No newline at end of file +window.addTestBack = addTestBack; +window.testHeadphoneAudio = testHeadphoneAudio; \ No newline at end of file