[Track Info] Add play count tabulation and display
Add play count data collection, including first seen times for every file first added to the playlist. Data is indexed by album, artist, and title, or by filename, whichever matches first. Add interfaces to AppleScript automation definition as well. Signed-off-by: Christopher Snowhill <kode54@gmail.com>swiftingly
parent
404d354918
commit
a2d8e0ec42
|
@ -717,6 +717,13 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
|
|||
[[NSNotificationCenter defaultCenter] postNotificationName:CogPlaybackDidBeginNotficiation object:pe];
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(AudioPlayer *)player reportPlayCountForTrack:(id)userInfo {
|
||||
if(userInfo) {
|
||||
PlaylistEntry *pe = (PlaylistEntry *)userInfo;
|
||||
[playlistController updatePlayCountForTrack:pe];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(AudioPlayer *)player setError:(NSNumber *)status toTrack:(id)userInfo {
|
||||
PlaylistEntry *pe = (PlaylistEntry *)userInfo;
|
||||
[pe setError:[status boolValue]];
|
||||
|
|
|
@ -77,6 +77,7 @@ using std::atomic_bool;
|
|||
- (double)volumeDown:(double)amount;
|
||||
|
||||
- (double)amountPlayed;
|
||||
- (double)amountPlayedInterval;
|
||||
|
||||
- (void)setNextStream:(NSURL *)url;
|
||||
- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi;
|
||||
|
@ -116,6 +117,7 @@ using std::atomic_bool;
|
|||
//- (BufferChain *)bufferChain;
|
||||
- (void)launchOutputThread;
|
||||
- (void)endOfInputPlayed;
|
||||
- (void)reportPlayCount;
|
||||
- (void)sendDelegateMethod:(SEL)selector withVoid:(void *)obj waitUntilDone:(BOOL)wait;
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait;
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj withObject:(id)obj2 waitUntilDone:(BOOL)wait;
|
||||
|
@ -134,5 +136,6 @@ using std::atomic_bool;
|
|||
- (void)audioPlayer:(AudioPlayer *)player sustainHDCD:(id)userInfo;
|
||||
- (void)audioPlayer:(AudioPlayer *)player restartPlaybackAtCurrentPosition:(id)userInfo;
|
||||
- (void)audioPlayer:(AudioPlayer *)player pushInfo:(NSDictionary *)info toTrack:(id)userInfo;
|
||||
- (void)audioPlayer:(AudioPlayer *)player reportPlayCountForTrack:(id)userInfo;
|
||||
- (void)audioPlayer:(AudioPlayer *)player setError:(NSNumber *)status toTrack:(id)userInfo;
|
||||
@end
|
||||
|
|
|
@ -226,6 +226,10 @@
|
|||
[self sendDelegateMethod:@selector(audioPlayer:pushInfo:toTrack:) withObject:info withObject:userInfo waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)reportPlayCountForTrack:(id)userInfo {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:reportPlayCountForTrack:) withObject:userInfo waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s {
|
||||
shouldContinue = s;
|
||||
|
||||
|
@ -240,6 +244,10 @@
|
|||
return [output amountPlayed];
|
||||
}
|
||||
|
||||
- (double)amountPlayedInterval {
|
||||
return [output amountPlayedInterval];
|
||||
}
|
||||
|
||||
- (void)launchOutputThread {
|
||||
initialBufferFilled = YES;
|
||||
if(outputLaunched == NO && startedPaused == NO) {
|
||||
|
@ -409,6 +417,12 @@
|
|||
return YES;
|
||||
}
|
||||
|
||||
- (void)reportPlayCount {
|
||||
if(bufferChain) {
|
||||
[self reportPlayCountForTrack:[bufferChain userInfo]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)endOfInputPlayed {
|
||||
// Once we get here:
|
||||
// - the buffer chain for the next playlist entry (started in endOfInputReached) have been working for some time
|
||||
|
|
|
@ -20,10 +20,12 @@
|
|||
uint32_t config;
|
||||
|
||||
double amountPlayed;
|
||||
double amountPlayedInterval;
|
||||
OutputCoreAudio *output;
|
||||
|
||||
BOOL paused;
|
||||
BOOL started;
|
||||
BOOL intervalReported;
|
||||
}
|
||||
|
||||
- (void)beginEqualizer:(AudioUnit)eq;
|
||||
|
@ -31,9 +33,11 @@
|
|||
- (void)endEqualizer:(AudioUnit)eq;
|
||||
|
||||
- (double)amountPlayed;
|
||||
- (double)amountPlayedInterval;
|
||||
|
||||
- (void)incrementAmountPlayed:(double)seconds;
|
||||
- (void)resetAmountPlayed;
|
||||
- (void)resetAmountPlayedInterval;
|
||||
|
||||
- (void)endOfInputPlayed;
|
||||
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
|
||||
- (void)setup {
|
||||
amountPlayed = 0.0;
|
||||
amountPlayedInterval = 0.0;
|
||||
|
||||
paused = YES;
|
||||
started = NO;
|
||||
intervalReported = NO;
|
||||
|
||||
output = [[OutputCoreAudio alloc] initWithController:self];
|
||||
|
||||
|
@ -50,14 +52,29 @@
|
|||
|
||||
- (void)incrementAmountPlayed:(double)seconds {
|
||||
amountPlayed += seconds;
|
||||
amountPlayedInterval += seconds;
|
||||
if(!intervalReported && amountPlayedInterval >= 60.0) {
|
||||
intervalReported = YES;
|
||||
[controller reportPlayCount];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resetAmountPlayed {
|
||||
amountPlayed = 0;
|
||||
}
|
||||
|
||||
- (void)resetAmountPlayedInterval {
|
||||
amountPlayedInterval = 0;
|
||||
intervalReported = NO;
|
||||
}
|
||||
|
||||
- (void)endOfInputPlayed {
|
||||
if(!intervalReported) {
|
||||
intervalReported = YES;
|
||||
[controller reportPlayCount];
|
||||
}
|
||||
[controller endOfInputPlayed];
|
||||
[self resetAmountPlayedInterval];
|
||||
}
|
||||
|
||||
- (BOOL)chainQueueHasTracks {
|
||||
|
@ -86,6 +103,10 @@
|
|||
return amountPlayed;
|
||||
}
|
||||
|
||||
- (double)amountPlayedInterval {
|
||||
return amountPlayedInterval;
|
||||
}
|
||||
|
||||
- (AudioStreamBasicDescription)format {
|
||||
return format;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -15,16 +15,16 @@
|
|||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Info Inspector" separatorStyle="none" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" visibleAtLaunch="NO" frameAutosaveName="InfoInspector" animationBehavior="default" titlebarAppearsTransparent="YES" id="1" customClass="NSPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<rect key="contentRect" x="700" y="80" width="300" height="560"/>
|
||||
<rect key="contentRect" x="700" y="80" width="300" height="582"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
|
||||
<value key="minSize" type="size" width="240" height="528"/>
|
||||
<value key="minSize" type="size" width="240" height="550"/>
|
||||
<value key="maxSize" type="size" width="400" height="600"/>
|
||||
<view key="contentView" id="2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="560"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="582"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9">
|
||||
<rect key="frame" x="60" y="504" width="45" height="14"/>
|
||||
<rect key="frame" x="60" y="526" width="45" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Artist:" id="10">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -33,7 +33,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="11">
|
||||
<rect key="frame" x="62" y="482" width="43" height="14"/>
|
||||
<rect key="frame" x="62" y="504" width="43" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album:" id="12">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -42,7 +42,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13">
|
||||
<rect key="frame" x="67" y="438" width="38" height="14"/>
|
||||
<rect key="frame" x="67" y="460" width="38" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Track:" id="14">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -51,7 +51,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="15">
|
||||
<rect key="frame" x="60" y="416" width="45" height="14"/>
|
||||
<rect key="frame" x="60" y="438" width="45" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Length:" id="16">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -60,7 +60,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="17">
|
||||
<rect key="frame" x="73" y="394" width="32" height="14"/>
|
||||
<rect key="frame" x="73" y="416" width="32" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Year:" id="18">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -69,7 +69,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="19">
|
||||
<rect key="frame" x="65" y="372" width="40" height="14"/>
|
||||
<rect key="frame" x="65" y="394" width="40" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Genre:" id="20">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -78,7 +78,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="21">
|
||||
<rect key="frame" x="32" y="328" width="73" height="14"/>
|
||||
<rect key="frame" x="32" y="350" width="73" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Sample Rate:" id="22">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -87,7 +87,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="27">
|
||||
<rect key="frame" x="48" y="306" width="57" height="14"/>
|
||||
<rect key="frame" x="48" y="328" width="57" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Channels:" id="28">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -96,7 +96,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="29">
|
||||
<rect key="frame" x="63" y="284" width="42" height="14"/>
|
||||
<rect key="frame" x="63" y="306" width="42" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bitrate:" id="32">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -105,7 +105,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="30">
|
||||
<rect key="frame" x="15" y="262" width="90" height="14"/>
|
||||
<rect key="frame" x="15" y="284" width="90" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bits Per Sample:" id="31">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -114,7 +114,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="23">
|
||||
<rect key="frame" x="73" y="460" width="32" height="14"/>
|
||||
<rect key="frame" x="73" y="482" width="32" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Title:" id="24">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -123,7 +123,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="504" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="526" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="34">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -135,7 +135,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="35" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="482" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="504" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="36">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -147,7 +147,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="37" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="460" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="482" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="38">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -159,7 +159,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="39" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="438" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="460" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="40">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -171,7 +171,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="41" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="416" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="438" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="42">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -183,7 +183,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="43" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="394" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="416" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="44">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -195,7 +195,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="45" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="372" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="394" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="46">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -207,7 +207,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="49" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="328" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="350" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="50">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -219,7 +219,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="51" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="306" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="328" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="52">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -231,7 +231,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="53" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="284" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="306" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="54">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -243,7 +243,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="55" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="262" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="284" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="56">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -255,7 +255,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPg-Mb-Urn">
|
||||
<rect key="frame" x="60" y="240" width="45" height="14"/>
|
||||
<rect key="frame" x="60" y="262" width="45" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Codec:" id="cbq-TT-CZX">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -264,7 +264,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ijS-y2-eCZ" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="240" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="262" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="Yby-OU-cqP">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -276,7 +276,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bti-s6-SIU">
|
||||
<rect key="frame" x="38" y="218" width="67" height="14"/>
|
||||
<rect key="frame" x="38" y="240" width="67" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Encoding:" id="8e7-lp-K5l">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -285,7 +285,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L4f-rE-CN3" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="218" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="240" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="v14-AG-B9D">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -297,7 +297,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gn9-9b-eV8">
|
||||
<rect key="frame" x="15" y="196" width="90" height="14"/>
|
||||
<rect key="frame" x="15" y="218" width="90" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Cuesheet:" id="Cu5-ia-Z5b">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -306,7 +306,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WOl-SC-4tu" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="196" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="218" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="UyE-Mc-e39">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -318,7 +318,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="arA-Pj-ANg">
|
||||
<rect key="frame" x="15" y="174" width="90" height="14"/>
|
||||
<rect key="frame" x="15" y="196" width="90" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="ReplayGain:" id="qBi-M8-kEx">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -327,7 +327,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2xx-It-i6I" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="174" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="196" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="rAo-AP-lVa">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -340,7 +340,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="84">
|
||||
<rect key="frame" x="49" y="350" width="56" height="14"/>
|
||||
<rect key="frame" x="49" y="372" width="56" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename:" id="85">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -349,7 +349,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="86" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="350" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="372" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="87">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -375,7 +375,7 @@
|
|||
</connections>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vB6-9J-5qg">
|
||||
<rect key="frame" x="18" y="526" width="87" height="14"/>
|
||||
<rect key="frame" x="18" y="548" width="87" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album Artist:" id="LFJ-QQ-gGr">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -384,7 +384,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cj0-Tw-xpq" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="526" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="548" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="B8w-o8-ZBw">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -395,6 +395,28 @@
|
|||
<binding destination="-2" name="value" keyPath="valueToDisplay.albumartist" id="gTS-bf-rHT"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FWx-Sc-Ocv">
|
||||
<rect key="frame" x="15" y="174" width="90" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Play Count:" id="fiv-eh-w3c">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WSs-wC-mWc" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="174" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" sendsActionOnEndEditing="YES" title="N/A" id="Ial-XI-91y">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.playCountInfo" id="ydF-ec-fBX"/>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.playCount" id="Mpe-FA-gRG"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
</view>
|
||||
<point key="canvasLocation" x="136" y="131"/>
|
||||
|
|
8
Cog.sdef
8
Cog.sdef
|
@ -168,6 +168,12 @@
|
|||
<property name="bitrate" code="pBit" description="The bitrate of the entry, in kilobits per second." type="integer" access="r">
|
||||
<cocoa key="bitrate" insert-at-beginning="yes"/>
|
||||
</property>
|
||||
<property name="playcount" code="pPlc" description="The play count of the entry." type="text" access="r">
|
||||
<cocoa key="playCount" insert-at-beginning="yes"/>
|
||||
</property>
|
||||
<property name="playinfo" code="pPli" description="The play count info of the entry, as either first seen time, or also last played time. May be empty string." type="text" access="r">
|
||||
<cocoa key="playCountInfo" access="r"/>
|
||||
</property>
|
||||
<property name="spam" code="pSpm" description="Formatted now playing spam for the entry." type="text" access="r">
|
||||
<cocoa key="spam"/>
|
||||
</property>
|
||||
|
@ -260,4 +266,4 @@
|
|||
<command name="previous" code="coreprev" description="Play previous track."/>
|
||||
<command name="next" code="corenext" description="Play next track."/>
|
||||
</suite>
|
||||
</dictionary>
|
||||
</dictionary>
|
||||
|
|
|
@ -4,6 +4,15 @@
|
|||
<attribute name="artData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="artHash" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="PlayCount" representedClassName="PlayCount" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="album" optional="YES" attributeType="String"/>
|
||||
<attribute name="artist" optional="YES" attributeType="String"/>
|
||||
<attribute name="count" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="filename" optional="YES" attributeType="String"/>
|
||||
<attribute name="firstSeen" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastPlayed" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="title" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="PlaylistEntry" representedClassName="PlaylistEntry" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="album" optional="YES" attributeType="String"/>
|
||||
<attribute name="albumartist" optional="YES" attributeType="String"/>
|
||||
|
@ -54,5 +63,6 @@
|
|||
<elements>
|
||||
<element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/>
|
||||
<element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="704"/>
|
||||
<element name="PlayCount" positionX="-18" positionY="171" width="128" height="134"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -134,6 +134,9 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
|
|||
// reload metadata of selection
|
||||
- (IBAction)reloadTags:(id _Nullable)sender;
|
||||
|
||||
// Play statistics
|
||||
- (void)updatePlayCountForTrack:(PlaylistEntry *)pe;
|
||||
|
||||
- (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *_Nullable)indexSet
|
||||
toIndex:(NSUInteger)insertIndex;
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#import "Logging.h"
|
||||
|
||||
#import "Cog-Swift.h"
|
||||
|
||||
#define UNDO_STACK_LIMIT 0
|
||||
|
||||
@implementation PlaylistController
|
||||
|
@ -242,6 +244,40 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
|
|||
}
|
||||
}
|
||||
|
||||
- (void)updatePlayCountForTrack:(PlaylistEntry *)pe {
|
||||
PlayCount *pc = pe.playCountItem;
|
||||
|
||||
if(pc) {
|
||||
pc.count += 1;
|
||||
pc.lastPlayed = [NSDate date];
|
||||
} else {
|
||||
pc = [NSEntityDescription insertNewObjectForEntityForName:@"PlayCount" inManagedObjectContext:self.persistentContainer.viewContext];
|
||||
pc.count = 1;
|
||||
pc.firstSeen = pc.lastPlayed = [NSDate date];
|
||||
pc.album = pe.album;
|
||||
pc.artist = pe.artist;
|
||||
pc.title = pe.title;
|
||||
pc.filename = pe.filename;
|
||||
}
|
||||
|
||||
[self commitEditing];
|
||||
}
|
||||
|
||||
- (void)firstSawTrack:(PlaylistEntry *)pe {
|
||||
PlayCount *pc = pe.playCountItem;
|
||||
|
||||
if(!pc) {
|
||||
pc = [NSEntityDescription insertNewObjectForEntityForName:@"PlayCount" inManagedObjectContext:self.persistentContainer.viewContext];
|
||||
pc.count = 0;
|
||||
pc.firstSeen = [NSDate date];
|
||||
pc.album = pe.album;
|
||||
pc.artist = pe.artist;
|
||||
pc.title = pe.title;
|
||||
pc.filename = pe.filename;
|
||||
[self commitEditing];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updatePlaylistIndexes {
|
||||
NSArray *arranged = [self arrangedObjects];
|
||||
NSUInteger n = [arranged count];
|
||||
|
@ -1587,6 +1623,10 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
|
|||
- (void)didInsertURLs:(NSArray *)urls origin:(URLOrigin)origin {
|
||||
if(![urls count]) return;
|
||||
|
||||
for(PlaylistEntry *pe in urls) {
|
||||
[self firstSawTrack:pe];
|
||||
}
|
||||
|
||||
CGEventRef event = CGEventCreate(NULL);
|
||||
CGEventFlags mods = CGEventGetFlags(event);
|
||||
CFRelease(event);
|
||||
|
|
|
@ -66,6 +66,10 @@
|
|||
@property(nonatomic) BOOL Unsigned;
|
||||
@property(nonatomic) NSURL *_Nullable URL;
|
||||
|
||||
@property(nonatomic) PlayCount *_Nullable playCountItem;
|
||||
@property(nonatomic, readonly) NSString *_Nonnull playCount;
|
||||
@property(nonatomic, readonly) NSString *_Nonnull playCountInfo;
|
||||
|
||||
- (void)setMetadata:(NSDictionary *_Nonnull)metadata;
|
||||
|
||||
@end
|
||||
|
|
|
@ -493,4 +493,58 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path) {
|
|||
[self setMetadataLoaded:YES];
|
||||
}
|
||||
|
||||
@dynamic playCountItem;
|
||||
- (PlayCount *)playCountItem {
|
||||
NSPredicate *albumPredicate = [NSPredicate predicateWithFormat:@"album == %@", self.album];
|
||||
NSPredicate *artistPredicate = [NSPredicate predicateWithFormat:@"artist == %@", self.artist];
|
||||
NSPredicate *titlePredicate = [NSPredicate predicateWithFormat:@"title == %@", self.title];
|
||||
|
||||
NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[albumPredicate, artistPredicate, titlePredicate]];
|
||||
|
||||
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
|
||||
request.predicate = predicate;
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray *results = [__persistentContainer.viewContext executeFetchRequest:request error:&error];
|
||||
|
||||
if(!results || [results count] < 1) {
|
||||
NSPredicate *filenamePredicate = [NSPredicate predicateWithFormat:@"filename == %@", self.filename];
|
||||
|
||||
request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
|
||||
request.predicate = filenamePredicate;
|
||||
|
||||
results = [__persistentContainer.viewContext executeFetchRequest:request error:&error];
|
||||
}
|
||||
|
||||
if(!results || [results count] < 1) return nil;
|
||||
|
||||
return results[0];
|
||||
}
|
||||
|
||||
@dynamic playCount;
|
||||
- (NSString *)playCount {
|
||||
PlayCount *pc = self.playCountItem;
|
||||
if(pc)
|
||||
return [NSString stringWithFormat:@"%llu", pc.count];
|
||||
else
|
||||
return @"0";
|
||||
}
|
||||
|
||||
@dynamic playCountInfo;
|
||||
- (NSString *)playCountInfo {
|
||||
PlayCount *pc = self.playCountItem;
|
||||
if(pc) {
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.dateStyle = NSDateFormatterMediumStyle;
|
||||
dateFormatter.timeStyle = NSDateFormatterShortStyle;
|
||||
|
||||
if(pc.count) {
|
||||
return [NSString stringWithFormat:@"%@: %@\n%@: %@", NSLocalizedStringFromTableInBundle(@"TimeFirstSeen", nil, [NSBundle bundleForClass:[self class]], @""), [dateFormatter stringFromDate:pc.firstSeen], NSLocalizedStringFromTableInBundle(@"TimeLastPlayed", nil, [NSBundle bundleForClass:[self class]], @""), [dateFormatter stringFromDate:pc.firstSeen]];
|
||||
} else {
|
||||
return [NSString stringWithFormat:@"%@: %@", NSLocalizedStringFromTableInBundle(@"TimeFirstSeen", nil, [NSBundle bundleForClass:[self class]], @""), [dateFormatter stringFromDate:pc.firstSeen]];
|
||||
}
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -62,3 +62,6 @@
|
|||
"ErrorInvalidTrackId" = "Invalid track ID sent to SQLite request.";
|
||||
"ErrorSqliteProblem" = "General problem accessing track from SQLite database.";
|
||||
"ErrorTrackMissing" = "Track entry is missing from SQLite database.";
|
||||
|
||||
"TimeLastPlayed" = "Last played";
|
||||
"TimeFirstSeen" = "First seen";
|
||||
|
|
|
@ -62,3 +62,6 @@
|
|||
"ErrorInvalidTrackId" = "Invalid track ID sent to SQLite request.";
|
||||
"ErrorSqliteProblem" = "General problem accessing track from SQLite database.";
|
||||
"ErrorTrackMissing" = "Track entry is missing from SQLite database.";
|
||||
|
||||
"TimeLastPlayed" = "Last played";
|
||||
"TimeFirstSeen" = "First seen";
|
||||
|
|
Loading…
Reference in New Issue